loading
Generated 2020-02-27T14:56:58-05:00

All Files ( 47.01% covered at 1.07 hits/line )

326 files in total.
13914 relevant lines, 6541 lines covered and 7373 lines missed. ( 47.01% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line
app/channels/application_cable/channel.rb 0.00 % 4 4 0 4 0.00
app/channels/application_cable/connection.rb 0.00 % 4 4 0 4 0.00
app/controllers/application_controller.rb 81.82 % 16 11 9 2 1.09
app/controllers/concerns/route_helper.rb 92.16 % 84 51 47 4 113.29
app/controllers/layouts_controller.rb 0.00 % 10 8 0 8 0.00
app/controllers/react_component_controller.rb 80.00 % 31 15 12 3 0.87
app/helpers/application_helper.rb 100.00 % 7 3 3 0 1.00
app/jobs/application_job.rb 0.00 % 2 2 0 2 0.00
app/mailers/application_mailer.rb 0.00 % 4 4 0 4 0.00
app/models/application_record.rb 100.00 % 3 2 2 0 1.00
app/models/user.rb 100.00 % 6 2 2 0 1.00
lib/logging/logging.rb 57.14 % 176 77 44 33 0.90
lib/props/prop_loader.rb 83.72 % 71 43 36 7 5.05
lib/utilities.rb 0.00 % 116 82 0 82 0.00
target/rubygems/gems/actioncable-5.2.3/lib/action_cable/channel.rb 100.00 % 16 10 10 0 1.00
target/rubygems/gems/actioncable-5.2.3/lib/action_cable/channel/base.rb 41.41 % 305 99 41 58 1.02
target/rubygems/gems/actioncable-5.2.3/lib/action_cable/channel/broadcasting.rb 64.29 % 31 14 9 5 0.64
target/rubygems/gems/actioncable-5.2.3/lib/action_cable/channel/callbacks.rb 90.00 % 37 20 18 2 0.95
target/rubygems/gems/actioncable-5.2.3/lib/action_cable/channel/naming.rb 87.50 % 25 8 7 1 0.88
target/rubygems/gems/actioncable-5.2.3/lib/action_cable/channel/periodic_timers.rb 46.88 % 78 32 15 17 0.47
target/rubygems/gems/actioncable-5.2.3/lib/action_cable/channel/streams.rb 39.13 % 176 46 18 28 0.39
target/rubygems/gems/actioncable-5.2.3/lib/action_cable/server.rb 100.00 % 17 10 10 0 1.00
target/rubygems/gems/actioncable-5.2.3/lib/action_cable/server/base.rb 61.11 % 89 36 22 14 0.61
target/rubygems/gems/actioncable-5.2.3/lib/action_cable/server/broadcasting.rb 55.56 % 54 18 10 8 0.56
target/rubygems/gems/actioncable-5.2.3/lib/action_cable/server/configuration.rb 58.33 % 56 24 14 10 0.58
target/rubygems/gems/actioncable-5.2.3/lib/action_cable/server/connections.rb 60.00 % 36 15 9 6 0.60
target/rubygems/gems/actioncable-5.2.3/lib/action_cable/server/worker.rb 54.05 % 77 37 20 17 0.54
target/rubygems/gems/actioncable-5.2.3/lib/action_cable/server/worker/active_record_connection_management.rb 90.00 % 21 10 9 1 0.90
target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/base.rb 42.19 % 991 192 81 111 0.42
target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/collector.rb 52.38 % 32 21 11 10 0.52
target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/delivery_job.rb 60.00 % 36 15 9 6 0.60
target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/delivery_methods.rb 67.65 % 82 34 23 11 0.97
target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/inline_preview_interceptor.rb 52.00 % 59 25 13 12 0.52
target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/log_subscriber.rb 42.11 % 41 19 8 11 0.42
target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/mail_helper.rb 30.43 % 72 23 7 16 0.30
target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/message_delivery.rb 42.86 % 144 35 15 20 0.43
target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/parameterized.rb 54.29 % 154 35 19 16 0.54
target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/preview.rb 55.77 % 126 52 29 23 0.56
target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/rescuable.rb 64.29 % 29 14 9 5 0.64
target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/test_case.rb 57.97 % 123 69 40 29 0.62
target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/test_helper.rb 37.50 % 144 24 9 15 0.38
target/rubygems/gems/actionpack-5.2.3/lib/action_controller/api.rb 80.00 % 149 15 12 3 1.67
target/rubygems/gems/actionpack-5.2.3/lib/action_controller/api/api_rendering.rb 75.00 % 16 8 6 2 0.75
target/rubygems/gems/actionpack-5.2.3/lib/action_controller/metal/testing.rb 62.50 % 16 8 5 3 0.63
target/rubygems/gems/actionpack-5.2.3/lib/action_controller/template_assertions.rb 75.00 % 11 4 3 1 0.75
target/rubygems/gems/actionpack-5.2.3/lib/action_controller/test_case.rb 33.20 % 629 250 83 167 0.33
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/callbacks.rb 88.89 % 36 18 16 2 1.22
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/debug_exceptions.rb 33.33 % 205 105 35 70 0.37
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/exception_wrapper.rb 37.10 % 147 62 23 39 0.37
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/executor.rb 100.00 % 21 11 11 0 2.82
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/flash.rb 52.31 % 300 130 68 62 0.58
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/public_exceptions.rb 40.00 % 57 25 10 15 0.40
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/reloader.rb 100.00 % 12 2 2 0 1.00
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/remote_ip.rb 83.67 % 183 49 41 8 1.53
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/request_id.rb 94.44 % 43 18 17 1 1.39
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/session/abstract_store.rb 76.47 % 92 51 39 12 1.10
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/session/cookie_store.rb 92.68 % 118 41 38 3 1.46
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/show_exceptions.rb 50.00 % 62 28 14 14 0.57
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/static.rb 58.33 % 130 60 35 25 1.05
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/request/session.rb 75.65 % 234 115 87 28 1.67
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/request/utils.rb 58.33 % 78 36 21 15 1.47
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/routing/inspector.rb 37.27 % 225 110 41 69 0.37
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/testing/assertion_response.rb 100.00 % 47 19 19 0 1.95
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/testing/assertions.rb 76.92 % 24 13 10 3 0.77
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/testing/assertions/response.rb 89.74 % 107 39 35 4 1.64
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/testing/assertions/routing.rb 19.23 % 222 78 15 63 0.19
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/testing/integration.rb 85.64 % 652 202 173 29 1.49
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/testing/request_encoder.rb 83.33 % 55 30 25 5 1.13
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/testing/test_process.rb 60.00 % 50 20 12 8 0.60
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/testing/test_request.rb 54.29 % 71 35 19 16 0.54
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/testing/test_response.rb 63.16 % 53 19 12 7 0.79
target/rubygems/gems/actionview-5.2.3/lib/action_view/buffers.rb 70.00 % 52 30 21 9 1.73
target/rubygems/gems/actionview-5.2.3/lib/action_view/dependency_tracker.rb 51.32 % 175 76 39 37 0.55
target/rubygems/gems/actionview-5.2.3/lib/action_view/digestor.rb 42.25 % 134 71 30 41 0.44
target/rubygems/gems/actionview-5.2.3/lib/action_view/flows.rb 47.37 % 76 38 18 20 0.47
target/rubygems/gems/actionview-5.2.3/lib/action_view/renderer/abstract_renderer.rb 95.00 % 55 20 19 1 3.50
target/rubygems/gems/actionview-5.2.3/lib/action_view/renderer/partial_renderer.rb 50.31 % 552 163 82 81 1.09
target/rubygems/gems/actionview-5.2.3/lib/action_view/renderer/partial_renderer/collection_caching.rb 36.67 % 57 30 11 19 0.37
target/rubygems/gems/actionview-5.2.3/lib/action_view/renderer/renderer.rb 78.95 % 56 19 15 4 1.00
target/rubygems/gems/actionview-5.2.3/lib/action_view/renderer/template_renderer.rb 64.29 % 102 56 36 20 0.71
target/rubygems/gems/actionview-5.2.3/lib/action_view/routing_url_for.rb 39.47 % 145 38 15 23 0.39
target/rubygems/gems/activejob-5.2.3/lib/active_job/arguments.rb 35.90 % 165 78 28 50 0.36
target/rubygems/gems/activejob-5.2.3/lib/active_job/base.rb 100.00 % 74 23 23 0 1.00
target/rubygems/gems/activejob-5.2.3/lib/active_job/callbacks.rb 87.50 % 155 24 21 3 0.92
target/rubygems/gems/activejob-5.2.3/lib/active_job/core.rb 49.02 % 158 51 25 26 0.49
target/rubygems/gems/activejob-5.2.3/lib/active_job/enqueuing.rb 45.00 % 59 20 9 11 0.45
target/rubygems/gems/activejob-5.2.3/lib/active_job/exceptions.rb 31.25 % 134 32 10 22 0.31
target/rubygems/gems/activejob-5.2.3/lib/active_job/execution.rb 52.38 % 49 21 11 10 0.52
target/rubygems/gems/activejob-5.2.3/lib/active_job/logging.rb 38.57 % 130 70 27 43 0.39
target/rubygems/gems/activejob-5.2.3/lib/active_job/queue_adapter.rb 75.00 % 60 28 21 7 0.93
target/rubygems/gems/activejob-5.2.3/lib/active_job/queue_adapters.rb 100.00 % 139 20 20 0 1.05
target/rubygems/gems/activejob-5.2.3/lib/active_job/queue_adapters/async_adapter.rb 59.09 % 116 44 26 18 0.68
target/rubygems/gems/activejob-5.2.3/lib/active_job/queue_name.rb 66.67 % 49 21 14 7 0.67
target/rubygems/gems/activejob-5.2.3/lib/active_job/queue_priority.rb 60.00 % 43 15 9 6 0.60
target/rubygems/gems/activejob-5.2.3/lib/active_job/test_helper.rb 27.87 % 456 122 34 88 0.28
target/rubygems/gems/activejob-5.2.3/lib/active_job/translation.rb 83.33 % 13 6 5 1 0.83
target/rubygems/gems/activerecord-5.2.3/lib/active_record/association_relation.rb 52.38 % 40 21 11 10 0.52
target/rubygems/gems/activerecord-5.2.3/lib/active_record/associations/collection_proxy.rb 47.42 % 1131 97 46 51 0.48
target/rubygems/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/transaction.rb 55.77 % 283 156 87 69 0.82
target/rubygems/gems/activerecord-5.2.3/lib/active_record/fixture_set/file.rb 39.53 % 82 43 17 26 0.40
target/rubygems/gems/activerecord-5.2.3/lib/active_record/fixtures.rb 44.51 % 1065 328 146 182 0.56
target/rubygems/gems/activerecord-5.2.3/lib/active_record/legacy_yaml_adapter.rb 34.78 % 48 23 8 15 0.39
target/rubygems/gems/activerecord-5.2.3/lib/active_record/railties/controller_runtime.rb 96.67 % 56 30 29 1 1.23
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation.rb 39.65 % 629 227 90 137 0.66
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/batches.rb 15.94 % 287 69 11 58 0.16
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/batches/batch_enumerator.rb 42.86 % 69 21 9 12 0.52
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/calculations.rb 20.13 % 417 154 31 123 0.27
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/finder_methods.rb 25.13 % 565 187 47 140 0.25
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/from_clause.rb 92.31 % 26 13 12 1 1.08
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/merger.rb 23.91 % 193 92 22 70 0.24
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder.rb 62.20 % 152 82 51 31 0.93
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/array_handler.rb 38.46 % 48 26 10 16 0.50
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/association_query_value.rb 50.00 % 46 22 11 11 0.50
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/base_handler.rb 88.89 % 19 9 8 1 1.00
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/basic_object_handler.rb 100.00 % 20 10 10 0 1.10
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb 46.15 % 56 26 12 14 0.46
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/range_handler.rb 47.62 % 42 21 10 11 0.52
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/relation_handler.rb 44.44 % 19 9 4 5 0.44
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/query_attribute.rb 69.57 % 45 23 16 7 0.96
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/query_methods.rb 45.78 % 1231 391 179 212 1.29
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/spawn_methods.rb 48.15 % 77 27 13 14 0.67
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/where_clause.rb 40.86 % 186 93 38 55 0.45
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/where_clause_factory.rb 82.35 % 34 17 14 3 0.82
target/rubygems/gems/activerecord-5.2.3/lib/active_record/result.rb 85.71 % 149 56 48 8 2.68
target/rubygems/gems/activerecord-5.2.3/lib/active_record/runtime_registry.rb 100.00 % 24 8 8 0 1.25
target/rubygems/gems/activerecord-5.2.3/lib/active_record/schema_migration.rb 65.38 % 56 26 17 9 0.85
target/rubygems/gems/activerecord-5.2.3/lib/active_record/statement_cache.rb 83.02 % 121 53 44 9 1.08
target/rubygems/gems/activerecord-5.2.3/lib/active_record/table_metadata.rb 67.44 % 82 43 29 14 0.79
target/rubygems/gems/activestorage-5.2.3/config/routes.rb 70.59 % 31 17 12 5 0.71
target/rubygems/gems/activestorage-5.2.3/lib/active_storage/attached.rb 63.16 % 40 19 12 7 0.63
target/rubygems/gems/activestorage-5.2.3/lib/active_storage/attached/macros.rb 12.50 % 110 32 4 28 0.13
target/rubygems/gems/activestorage-5.2.3/lib/active_storage/attached/many.rb 50.00 % 59 14 7 7 0.50
target/rubygems/gems/activestorage-5.2.3/lib/active_storage/attached/one.rb 40.63 % 82 32 13 19 0.41
target/rubygems/gems/activesupport-5.2.3/lib/active_support/all.rb 100.00 % 5 3 3 0 1.00
target/rubygems/gems/activesupport-5.2.3/lib/active_support/backtrace_cleaner.rb 53.33 % 105 30 16 14 0.63
target/rubygems/gems/activesupport-5.2.3/lib/active_support/cache/memory_store.rb 32.97 % 169 91 30 61 0.33
target/rubygems/gems/activesupport-5.2.3/lib/active_support/cache/null_store.rb 86.67 % 43 15 13 2 0.87
target/rubygems/gems/activesupport-5.2.3/lib/active_support/cache/strategy/local_cache.rb 44.66 % 194 103 46 57 0.54
target/rubygems/gems/activesupport-5.2.3/lib/active_support/cache/strategy/local_cache_middleware.rb 95.83 % 45 24 23 1 1.25
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext.rb 100.00 % 5 2 2 0 12.00
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/big_decimal.rb 100.00 % 3 1 1 0 1.00
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/digest/uuid.rb 44.00 % 53 25 11 14 0.44
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/file.rb 100.00 % 3 1 1 0 1.00
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/file/atomic.rb 17.39 % 70 23 4 19 0.17
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/hash.rb 100.00 % 11 9 9 0 1.00
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/integer.rb 100.00 % 5 3 3 0 1.00
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/integer/inflections.rb 66.67 % 31 6 4 2 0.67
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/integer/multiple.rb 66.67 % 12 3 2 1 0.67
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/kernel.rb 100.00 % 6 4 4 0 1.00
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/kernel/agnostics.rb 50.00 % 13 4 2 2 0.50
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/kernel/concern.rb 80.00 % 14 5 4 1 0.80
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/marshal.rb 45.45 % 24 11 5 6 0.45
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/numeric.rb 100.00 % 6 4 4 0 1.00
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/numeric/conversions.rb 47.83 % 140 23 11 12 9.17
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/numeric/inquiry.rb 11.11 % 28 9 1 8 0.11
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/securerandom.rb 50.00 % 25 8 4 4 0.50
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/string.rb 100.00 % 15 13 13 0 1.00
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/string/exclude.rb 66.67 % 13 3 2 1 0.67
target/rubygems/gems/activesupport-5.2.3/lib/active_support/current_attributes.rb 47.06 % 195 51 24 27 0.59
target/rubygems/gems/activesupport-5.2.3/lib/active_support/digest.rb 80.00 % 20 10 8 2 0.80
target/rubygems/gems/activesupport-5.2.3/lib/active_support/test_case.rb 97.62 % 72 42 41 1 1.14
target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/assertions.rb 18.52 % 214 54 10 44 0.19
target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/autorun.rb 100.00 % 7 3 3 0 1.00
target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/constant_lookup.rb 53.33 % 51 15 8 7 0.53
target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/declarative.rb 83.33 % 28 12 10 2 1.25
target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/deprecation.rb 32.00 % 39 25 8 17 0.32
target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/file_fixtures.rb 58.33 % 36 12 7 5 0.58
target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/isolation.rb 22.95 % 110 61 14 47 0.23
target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/setup_and_teardown.rb 95.24 % 55 21 20 1 1.48
target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/stream.rb 25.93 % 44 27 7 20 0.26
target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/tagged_logging.rb 100.00 % 27 15 15 0 2.07
target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/time_helpers.rb 50.88 % 200 57 29 28 0.63
target/rubygems/gems/devise-4.7.1/app/controllers/devise_controller.rb 39.76 % 212 83 33 50 0.40
target/rubygems/gems/devise-4.7.1/app/helpers/devise_helper.rb 40.00 % 18 5 2 3 0.40
target/rubygems/gems/devise-4.7.1/lib/devise/controllers/scoped_views.rb 77.78 % 19 9 7 2 0.78
target/rubygems/gems/devise-4.7.1/lib/devise/controllers/url_helpers.rb 79.17 % 69 24 19 5 12.83
target/rubygems/gems/devise-4.7.1/lib/devise/delegator.rb 50.00 % 18 8 4 4 0.50
target/rubygems/gems/devise-4.7.1/lib/devise/failure_app.rb 30.22 % 285 139 42 97 0.30
target/rubygems/gems/devise-4.7.1/lib/devise/hooks/forgetable.rb 33.33 % 11 3 1 2 0.33
target/rubygems/gems/devise-4.7.1/lib/devise/hooks/rememberable.rb 80.00 % 9 5 4 1 1.20
target/rubygems/gems/devise-4.7.1/lib/devise/models/database_authenticatable.rb 41.67 % 237 84 35 49 0.43
target/rubygems/gems/devise-4.7.1/lib/devise/models/recoverable.rb 33.33 % 167 63 21 42 0.33
target/rubygems/gems/devise-4.7.1/lib/devise/models/registerable.rb 80.00 % 29 10 8 2 0.80
target/rubygems/gems/devise-4.7.1/lib/devise/models/rememberable.rb 43.64 % 158 55 24 31 0.44
target/rubygems/gems/devise-4.7.1/lib/devise/models/validatable.rb 77.42 % 73 31 24 7 0.94
target/rubygems/gems/devise-4.7.1/lib/devise/orm/active_record.rb 100.00 % 7 3 3 0 1.00
target/rubygems/gems/devise-4.7.1/lib/devise/secret_key_finder.rb 76.92 % 27 13 10 3 0.77
target/rubygems/gems/devise-4.7.1/lib/devise/strategies/authenticatable.rb 41.43 % 178 70 29 41 0.41
target/rubygems/gems/devise-4.7.1/lib/devise/strategies/base.rb 50.00 % 22 10 5 5 0.50
target/rubygems/gems/devise-4.7.1/lib/devise/strategies/database_authenticatable.rb 40.00 % 31 15 6 9 0.40
target/rubygems/gems/devise-4.7.1/lib/devise/strategies/rememberable.rb 46.43 % 67 28 13 15 0.46
target/rubygems/gems/devise-4.7.1/lib/devise/token_generator.rb 58.82 % 32 17 10 7 0.59
target/rubygems/gems/globalid-0.4.2/lib/global_id/identification.rb 71.43 % 25 14 10 4 0.71
target/rubygems/gems/globalid-0.4.2/lib/global_id/signed_global_id.rb 53.33 % 85 45 24 21 0.53
target/rubygems/gems/globalid-0.4.2/lib/global_id/verifier.rb 77.78 % 15 9 7 2 0.78
target/rubygems/gems/i18n-1.6.0/lib/i18n/backend.rb 100.00 % 21 17 17 0 1.00
target/rubygems/gems/i18n-1.6.0/lib/i18n/backend/base.rb 25.56 % 284 133 34 99 0.29
target/rubygems/gems/i18n-1.6.0/lib/i18n/backend/simple.rb 43.75 % 111 48 21 27 0.60
target/rubygems/gems/i18n-1.6.0/lib/i18n/backend/transliterator.rb 42.11 % 108 38 16 22 0.42
target/rubygems/gems/i18n-1.6.0/lib/i18n/core_ext/hash.rb 44.00 % 47 25 11 14 0.44
target/rubygems/gems/jbuilder-2.9.1/lib/jbuilder/dependency_tracker.rb 75.00 % 61 24 18 6 0.75
target/rubygems/gems/jdbc-postgres-42.2.6/lib/jdbc/postgres.rb 66.67 % 53 33 22 11 0.70
target/rubygems/gems/jdbc-postgres-42.2.6/lib/jdbc/postgres/version.rb 100.00 % 6 4 4 0 1.00
target/rubygems/gems/mail-2.7.1/lib/mail.rb 87.50 % 85 48 42 6 3.13
target/rubygems/gems/mail-2.7.1/lib/mail/attachments_list.rb 14.04 % 110 57 8 49 0.14
target/rubygems/gems/mail-2.7.1/lib/mail/body.rb 29.27 % 328 123 36 87 0.29
target/rubygems/gems/mail-2.7.1/lib/mail/check_delivery_params.rb 31.03 % 60 29 9 20 0.31
target/rubygems/gems/mail-2.7.1/lib/mail/configuration.rb 29.41 % 78 34 10 24 0.29
target/rubygems/gems/mail-2.7.1/lib/mail/constants.rb 100.00 % 57 46 46 0 1.00
target/rubygems/gems/mail-2.7.1/lib/mail/core_extensions/smtp.rb 9.09 % 28 11 1 10 0.09
target/rubygems/gems/mail-2.7.1/lib/mail/core_extensions/string.rb 42.86 % 17 7 3 4 0.43
target/rubygems/gems/mail-2.7.1/lib/mail/elements.rb 100.00 % 15 13 13 0 1.00
target/rubygems/gems/mail-2.7.1/lib/mail/encodings.rb 26.19 % 343 126 33 93 0.37
target/rubygems/gems/mail-2.7.1/lib/mail/encodings/7bit.rb 81.82 % 22 11 9 2 0.82
target/rubygems/gems/mail-2.7.1/lib/mail/encodings/8bit.rb 88.89 % 18 9 8 1 0.89
target/rubygems/gems/mail-2.7.1/lib/mail/encodings/base64.rb 70.59 % 38 17 12 5 0.71
target/rubygems/gems/mail-2.7.1/lib/mail/encodings/binary.rb 100.00 % 13 7 7 0 1.00
target/rubygems/gems/mail-2.7.1/lib/mail/encodings/identity.rb 70.00 % 24 10 7 3 0.70
target/rubygems/gems/mail-2.7.1/lib/mail/encodings/quoted_printable.rb 65.00 % 45 20 13 7 0.65
target/rubygems/gems/mail-2.7.1/lib/mail/encodings/transfer_encoding.rb 37.14 % 77 35 13 22 0.37
target/rubygems/gems/mail-2.7.1/lib/mail/encodings/unix_to_unix.rb 81.82 % 20 11 9 2 0.82
target/rubygems/gems/mail-2.7.1/lib/mail/envelope.rb 60.00 % 31 10 6 4 0.60
target/rubygems/gems/mail-2.7.1/lib/mail/field.rb 43.86 % 299 114 50 64 0.68
target/rubygems/gems/mail-2.7.1/lib/mail/field_list.rb 33.33 % 34 12 4 8 0.33
target/rubygems/gems/mail-2.7.1/lib/mail/fields.rb 100.00 % 36 33 33 0 1.00
target/rubygems/gems/mail-2.7.1/lib/mail/fields/bcc_field.rb 55.00 % 68 20 11 9 0.55
target/rubygems/gems/mail-2.7.1/lib/mail/fields/cc_field.rb 64.29 % 55 14 9 5 0.64
target/rubygems/gems/mail-2.7.1/lib/mail/fields/comments_field.rb 55.56 % 42 9 5 4 0.56
target/rubygems/gems/mail-2.7.1/lib/mail/fields/common/address_container.rb 57.14 % 17 7 4 3 0.57
target/rubygems/gems/mail-2.7.1/lib/mail/fields/common/common_address.rb 31.71 % 161 82 26 56 0.32
target/rubygems/gems/mail-2.7.1/lib/mail/fields/common/common_date.rb 52.94 % 36 17 9 8 0.53
target/rubygems/gems/mail-2.7.1/lib/mail/fields/common/common_field.rb 51.85 % 52 27 14 13 0.52
target/rubygems/gems/mail-2.7.1/lib/mail/fields/common/common_message_id.rb 45.83 % 49 24 11 13 0.46
target/rubygems/gems/mail-2.7.1/lib/mail/fields/common/parameter_hash.rb 20.69 % 59 29 6 23 0.21
target/rubygems/gems/mail-2.7.1/lib/mail/fields/content_description_field.rb 55.56 % 20 9 5 4 0.56
target/rubygems/gems/mail-2.7.1/lib/mail/fields/content_disposition_field.rb 34.21 % 71 38 13 25 0.34
target/rubygems/gems/mail-2.7.1/lib/mail/fields/content_id_field.rb 45.16 % 63 31 14 17 0.45
target/rubygems/gems/mail-2.7.1/lib/mail/fields/content_location_field.rb 50.00 % 43 20 10 10 0.50
target/rubygems/gems/mail-2.7.1/lib/mail/fields/content_transfer_encoding_field.rb 45.45 % 45 22 10 12 0.45
target/rubygems/gems/mail-2.7.1/lib/mail/fields/content_type_field.rb 26.26 % 197 99 26 73 0.26
target/rubygems/gems/mail-2.7.1/lib/mail/fields/date_field.rb 50.00 % 57 18 9 9 0.50
target/rubygems/gems/mail-2.7.1/lib/mail/fields/from_field.rb 64.29 % 55 14 9 5 0.64
target/rubygems/gems/mail-2.7.1/lib/mail/fields/in_reply_to_field.rb 56.25 % 57 16 9 7 0.56
target/rubygems/gems/mail-2.7.1/lib/mail/fields/keywords_field.rb 52.38 % 44 21 11 10 0.52
target/rubygems/gems/mail-2.7.1/lib/mail/fields/message_id_field.rb 50.00 % 83 28 14 14 0.50
target/rubygems/gems/mail-2.7.1/lib/mail/fields/mime_version_field.rb 46.15 % 54 26 12 14 0.46
target/rubygems/gems/mail-2.7.1/lib/mail/fields/received_field.rb 42.86 % 76 28 12 16 0.43
target/rubygems/gems/mail-2.7.1/lib/mail/fields/references_field.rb 56.25 % 57 16 9 7 0.56
target/rubygems/gems/mail-2.7.1/lib/mail/fields/reply_to_field.rb 64.29 % 55 14 9 5 0.64
target/rubygems/gems/mail-2.7.1/lib/mail/fields/resent_bcc_field.rb 64.29 % 55 14 9 5 0.64
target/rubygems/gems/mail-2.7.1/lib/mail/fields/resent_cc_field.rb 64.29 % 55 14 9 5 0.64
target/rubygems/gems/mail-2.7.1/lib/mail/fields/resent_date_field.rb 52.94 % 35 17 9 8 0.53
target/rubygems/gems/mail-2.7.1/lib/mail/fields/resent_from_field.rb 64.29 % 55 14 9 5 0.64
target/rubygems/gems/mail-2.7.1/lib/mail/fields/resent_message_id_field.rb 58.82 % 35 17 10 7 0.59
target/rubygems/gems/mail-2.7.1/lib/mail/fields/resent_sender_field.rb 61.11 % 62 18 11 7 0.61
target/rubygems/gems/mail-2.7.1/lib/mail/fields/resent_to_field.rb 64.29 % 55 14 9 5 0.64
target/rubygems/gems/mail-2.7.1/lib/mail/fields/return_path_field.rb 57.89 % 65 19 11 8 0.58
target/rubygems/gems/mail-2.7.1/lib/mail/fields/sender_field.rb 60.00 % 67 20 12 8 0.60
target/rubygems/gems/mail-2.7.1/lib/mail/fields/structured_field.rb 55.56 % 52 18 10 8 0.56
target/rubygems/gems/mail-2.7.1/lib/mail/fields/subject_field.rb 71.43 % 17 7 5 2 0.71
target/rubygems/gems/mail-2.7.1/lib/mail/fields/to_field.rb 64.29 % 55 14 9 5 0.64
target/rubygems/gems/mail-2.7.1/lib/mail/fields/unstructured_field.rb 21.65 % 222 97 21 76 0.22
target/rubygems/gems/mail-2.7.1/lib/mail/header.rb 35.48 % 278 93 33 60 0.35
target/rubygems/gems/mail-2.7.1/lib/mail/indifferent_hash.rb 53.57 % 147 56 30 26 0.54
target/rubygems/gems/mail-2.7.1/lib/mail/mail.rb 51.67 % 262 60 31 29 0.52
target/rubygems/gems/mail-2.7.1/lib/mail/matchers/attachment_matchers.rb 66.67 % 29 15 10 5 0.67
target/rubygems/gems/mail-2.7.1/lib/mail/matchers/has_sent_mail.rb 32.20 % 201 118 38 80 0.32
target/rubygems/gems/mail-2.7.1/lib/mail/message.rb 30.83 % 2170 639 197 442 0.31
target/rubygems/gems/mail-2.7.1/lib/mail/multibyte.rb 61.11 % 92 18 11 7 0.61
target/rubygems/gems/mail-2.7.1/lib/mail/multibyte/chars.rb 24.85 % 476 165 41 124 0.33
target/rubygems/gems/mail-2.7.1/lib/mail/multibyte/unicode.rb 27.23 % 405 202 55 147 0.61
target/rubygems/gems/mail-2.7.1/lib/mail/multibyte/utils.rb 36.00 % 61 25 9 16 0.36
target/rubygems/gems/mail-2.7.1/lib/mail/network.rb 100.00 % 16 12 12 0 1.00
target/rubygems/gems/mail-2.7.1/lib/mail/network/delivery_methods/file_delivery.rb 50.00 % 42 16 8 8 0.50
target/rubygems/gems/mail-2.7.1/lib/mail/network/delivery_methods/sendmail.rb 44.00 % 95 25 11 14 0.44
target/rubygems/gems/mail-2.7.1/lib/mail/network/delivery_methods/smtp.rb 32.35 % 149 34 11 23 0.32
target/rubygems/gems/mail-2.7.1/lib/mail/network/delivery_methods/test_mailer.rb 61.54 % 42 13 8 5 0.62
target/rubygems/gems/mail-2.7.1/lib/mail/network/retriever_methods/base.rb 30.00 % 64 20 6 14 0.30
target/rubygems/gems/mail-2.7.1/lib/mail/part.rb 41.82 % 123 55 23 32 0.42
target/rubygems/gems/mail-2.7.1/lib/mail/parts_list.rb 43.24 % 74 37 16 21 0.43
target/rubygems/gems/mail-2.7.1/lib/mail/utilities.rb 36.94 % 324 111 41 70 0.43
target/rubygems/gems/mail-2.7.1/lib/mail/version.rb 88.89 % 17 9 8 1 0.89
target/rubygems/gems/mail-2.7.1/lib/mail/version_specific/ruby_1_9.rb 26.67 % 278 135 36 99 0.27
target/rubygems/gems/mini_mime-1.0.2/lib/mini_mime.rb 37.50 % 166 96 36 60 0.38
target/rubygems/gems/mini_mime-1.0.2/lib/mini_mime/version.rb 100.00 % 3 2 2 0 1.00
target/rubygems/gems/minitest-5.11.3/lib/minitest/pride_plugin.rb 37.93 % 142 58 22 36 0.38
target/rubygems/gems/multi_json-1.13.1/lib/multi_json/adapter.rb 74.07 % 49 27 20 7 0.93
target/rubygems/gems/multi_json-1.13.1/lib/multi_json/adapters/json_common.rb 69.23 % 23 13 9 4 1.00
target/rubygems/gems/multi_json-1.13.1/lib/multi_json/adapters/json_gem.rb 100.00 % 11 6 6 0 1.00
target/rubygems/gems/rack-2.0.7/lib/rack/conditional_get.rb 54.29 % 79 35 19 16 0.69
target/rubygems/gems/rack-2.0.7/lib/rack/etag.rb 97.37 % 74 38 37 1 1.13
target/rubygems/gems/rack-2.0.7/lib/rack/file.rb 33.71 % 176 89 30 59 0.34
target/rubygems/gems/rack-2.0.7/lib/rack/head.rb 81.82 % 25 11 9 2 1.18
target/rubygems/gems/rack-2.0.7/lib/rack/lint.rb 18.41 % 760 239 44 195 0.18
target/rubygems/gems/rack-2.0.7/lib/rack/method_override.rb 57.14 % 50 28 16 12 0.68
target/rubygems/gems/rack-2.0.7/lib/rack/mime.rb 63.64 % 677 11 7 4 0.64
target/rubygems/gems/rack-2.0.7/lib/rack/mock.rb 66.67 % 196 96 64 32 1.53
target/rubygems/gems/rack-2.0.7/lib/rack/runtime.rb 100.00 % 32 16 16 0 1.38
target/rubygems/gems/rack-2.0.7/lib/rack/sendfile.rb 40.54 % 158 37 15 22 0.49
target/rubygems/gems/rack-2.0.7/lib/rack/session/cookie.rb 42.86 % 195 84 36 48 0.43
target/rubygems/gems/rack-2.0.7/lib/rack/tempfile_reaper.rb 100.00 % 22 11 11 0 1.45
target/rubygems/gems/rails-dom-testing-2.0.3/lib/rails-dom-testing.rb 100.00 % 1 1 1 0 1.00
target/rubygems/gems/rails-dom-testing-2.0.3/lib/rails/dom/testing/assertions.rb 100.00 % 18 11 11 0 1.00
target/rubygems/gems/rails-dom-testing-2.0.3/lib/rails/dom/testing/assertions/dom_assertions.rb 38.89 % 76 36 14 22 0.39
target/rubygems/gems/rails-dom-testing-2.0.3/lib/rails/dom/testing/assertions/selector_assertions.rb 30.51 % 302 59 18 41 0.31
target/rubygems/gems/rails-dom-testing-2.0.3/lib/rails/dom/testing/assertions/selector_assertions/count_describable.rb 61.11 % 32 18 11 7 0.61
target/rubygems/gems/rails-dom-testing-2.0.3/lib/rails/dom/testing/assertions/selector_assertions/html_selector.rb 22.22 % 114 63 14 49 0.24
target/rubygems/gems/rails-dom-testing-2.0.3/lib/rails/dom/testing/assertions/selector_assertions/substitution_context.rb 47.06 % 33 17 8 9 0.47
target/rubygems/gems/railties-5.2.3/lib/minitest/rails_plugin.rb 86.67 % 63 30 26 4 1.00
target/rubygems/gems/railties-5.2.3/lib/rails/application/bootstrap.rb 80.43 % 89 46 37 9 0.80
target/rubygems/gems/railties-5.2.3/lib/rails/application/default_middleware_stack.rb 78.95 % 107 57 45 12 0.79
target/rubygems/gems/railties-5.2.3/lib/rails/application/finisher.rb 73.26 % 196 86 63 23 0.78
target/rubygems/gems/railties-5.2.3/lib/rails/application/routes_reloader.rb 100.00 % 55 31 31 0 1.26
target/rubygems/gems/railties-5.2.3/lib/rails/backtrace_cleaner.rb 100.00 % 34 23 23 0 1.22
target/rubygems/gems/railties-5.2.3/lib/rails/generators/test_case.rb 100.00 % 37 13 13 0 1.00
target/rubygems/gems/railties-5.2.3/lib/rails/generators/testing/assertions.rb 40.54 % 127 37 15 22 0.41
target/rubygems/gems/railties-5.2.3/lib/rails/generators/testing/behaviour.rb 66.67 % 111 45 30 15 0.67
target/rubygems/gems/railties-5.2.3/lib/rails/generators/testing/setup_and_teardown.rb 54.55 % 20 11 6 5 0.55
target/rubygems/gems/railties-5.2.3/lib/rails/rack/logger.rb 84.09 % 80 44 37 7 1.41
target/rubygems/gems/railties-5.2.3/lib/rails/test_help.rb 84.62 % 48 26 22 4 0.92
target/rubygems/gems/railties-5.2.3/lib/rails/test_unit/reporter.rb 48.39 % 110 62 30 32 0.66
target/rubygems/gems/responders-3.0.0/lib/responders/flash_responder.rb 27.54 % 206 69 19 50 0.28
target/rubygems/gems/sprockets-3.7.2/lib/sprockets/autoload/sass.rb 100.00 % 7 4 4 0 1.00
target/rubygems/gems/sprockets-3.7.2/lib/sprockets/cache/file_store.rb 31.65 % 186 79 25 54 0.32
target/rubygems/gems/thor-0.20.3/lib/thor/shell/basic.rb 20.83 % 482 216 45 171 0.21
target/rubygems/gems/tzinfo-data-1.2019.2/lib/tzinfo/data/definitions/Etc/UTC.rb 100.00 % 21 8 8 0 1.00

Controllers ( 60.12% covered at 34.74 hits/line )

5 files in total.
168 relevant lines, 101 lines covered and 67 lines missed. ( 60.12% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line
app/controllers/application_controller.rb 81.82 % 16 11 9 2 1.09
app/controllers/concerns/route_helper.rb 92.16 % 84 51 47 4 113.29
app/controllers/layouts_controller.rb 0.00 % 10 8 0 8 0.00
app/controllers/react_component_controller.rb 80.00 % 31 15 12 3 0.87
target/rubygems/gems/devise-4.7.1/app/controllers/devise_controller.rb 39.76 % 212 83 33 50 0.40

Channels ( 0.0% covered at 0.0 hits/line )

2 files in total.
8 relevant lines, 0 lines covered and 8 lines missed. ( 0.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line
app/channels/application_cable/channel.rb 0.00 % 4 4 0 4 0.00
app/channels/application_cable/connection.rb 0.00 % 4 4 0 4 0.00

Models ( 100.0% covered at 1.0 hits/line )

2 files in total.
4 relevant lines, 4 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line
app/models/application_record.rb 100.00 % 3 2 2 0 1.00
app/models/user.rb 100.00 % 6 2 2 0 1.00

Mailers ( 0.0% covered at 0.0 hits/line )

1 files in total.
4 relevant lines, 0 lines covered and 4 lines missed. ( 0.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line
app/mailers/application_mailer.rb 0.00 % 4 4 0 4 0.00

Helpers ( 62.5% covered at 0.63 hits/line )

2 files in total.
8 relevant lines, 5 lines covered and 3 lines missed. ( 62.5% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line
app/helpers/application_helper.rb 100.00 % 7 3 3 0 1.00
target/rubygems/gems/devise-4.7.1/app/helpers/devise_helper.rb 40.00 % 18 5 2 3 0.40

Jobs ( 0.0% covered at 0.0 hits/line )

1 files in total.
2 relevant lines, 0 lines covered and 2 lines missed. ( 0.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line
app/jobs/application_job.rb 0.00 % 2 2 0 2 0.00

Libraries ( 46.84% covered at 0.66 hits/line )

312 files in total.
13703 relevant lines, 6419 lines covered and 7284 lines missed. ( 46.84% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line
lib/logging/logging.rb 57.14 % 176 77 44 33 0.90
lib/props/prop_loader.rb 83.72 % 71 43 36 7 5.05
lib/utilities.rb 0.00 % 116 82 0 82 0.00
target/rubygems/gems/actioncable-5.2.3/lib/action_cable/channel.rb 100.00 % 16 10 10 0 1.00
target/rubygems/gems/actioncable-5.2.3/lib/action_cable/channel/base.rb 41.41 % 305 99 41 58 1.02
target/rubygems/gems/actioncable-5.2.3/lib/action_cable/channel/broadcasting.rb 64.29 % 31 14 9 5 0.64
target/rubygems/gems/actioncable-5.2.3/lib/action_cable/channel/callbacks.rb 90.00 % 37 20 18 2 0.95
target/rubygems/gems/actioncable-5.2.3/lib/action_cable/channel/naming.rb 87.50 % 25 8 7 1 0.88
target/rubygems/gems/actioncable-5.2.3/lib/action_cable/channel/periodic_timers.rb 46.88 % 78 32 15 17 0.47
target/rubygems/gems/actioncable-5.2.3/lib/action_cable/channel/streams.rb 39.13 % 176 46 18 28 0.39
target/rubygems/gems/actioncable-5.2.3/lib/action_cable/server.rb 100.00 % 17 10 10 0 1.00
target/rubygems/gems/actioncable-5.2.3/lib/action_cable/server/base.rb 61.11 % 89 36 22 14 0.61
target/rubygems/gems/actioncable-5.2.3/lib/action_cable/server/broadcasting.rb 55.56 % 54 18 10 8 0.56
target/rubygems/gems/actioncable-5.2.3/lib/action_cable/server/configuration.rb 58.33 % 56 24 14 10 0.58
target/rubygems/gems/actioncable-5.2.3/lib/action_cable/server/connections.rb 60.00 % 36 15 9 6 0.60
target/rubygems/gems/actioncable-5.2.3/lib/action_cable/server/worker.rb 54.05 % 77 37 20 17 0.54
target/rubygems/gems/actioncable-5.2.3/lib/action_cable/server/worker/active_record_connection_management.rb 90.00 % 21 10 9 1 0.90
target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/base.rb 42.19 % 991 192 81 111 0.42
target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/collector.rb 52.38 % 32 21 11 10 0.52
target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/delivery_job.rb 60.00 % 36 15 9 6 0.60
target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/delivery_methods.rb 67.65 % 82 34 23 11 0.97
target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/inline_preview_interceptor.rb 52.00 % 59 25 13 12 0.52
target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/log_subscriber.rb 42.11 % 41 19 8 11 0.42
target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/mail_helper.rb 30.43 % 72 23 7 16 0.30
target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/message_delivery.rb 42.86 % 144 35 15 20 0.43
target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/parameterized.rb 54.29 % 154 35 19 16 0.54
target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/preview.rb 55.77 % 126 52 29 23 0.56
target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/rescuable.rb 64.29 % 29 14 9 5 0.64
target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/test_case.rb 57.97 % 123 69 40 29 0.62
target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/test_helper.rb 37.50 % 144 24 9 15 0.38
target/rubygems/gems/actionpack-5.2.3/lib/action_controller/api.rb 80.00 % 149 15 12 3 1.67
target/rubygems/gems/actionpack-5.2.3/lib/action_controller/api/api_rendering.rb 75.00 % 16 8 6 2 0.75
target/rubygems/gems/actionpack-5.2.3/lib/action_controller/metal/testing.rb 62.50 % 16 8 5 3 0.63
target/rubygems/gems/actionpack-5.2.3/lib/action_controller/template_assertions.rb 75.00 % 11 4 3 1 0.75
target/rubygems/gems/actionpack-5.2.3/lib/action_controller/test_case.rb 33.20 % 629 250 83 167 0.33
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/callbacks.rb 88.89 % 36 18 16 2 1.22
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/debug_exceptions.rb 33.33 % 205 105 35 70 0.37
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/exception_wrapper.rb 37.10 % 147 62 23 39 0.37
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/executor.rb 100.00 % 21 11 11 0 2.82
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/flash.rb 52.31 % 300 130 68 62 0.58
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/public_exceptions.rb 40.00 % 57 25 10 15 0.40
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/reloader.rb 100.00 % 12 2 2 0 1.00
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/remote_ip.rb 83.67 % 183 49 41 8 1.53
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/request_id.rb 94.44 % 43 18 17 1 1.39
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/session/abstract_store.rb 76.47 % 92 51 39 12 1.10
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/session/cookie_store.rb 92.68 % 118 41 38 3 1.46
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/show_exceptions.rb 50.00 % 62 28 14 14 0.57
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/static.rb 58.33 % 130 60 35 25 1.05
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/request/session.rb 75.65 % 234 115 87 28 1.67
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/request/utils.rb 58.33 % 78 36 21 15 1.47
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/routing/inspector.rb 37.27 % 225 110 41 69 0.37
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/testing/assertion_response.rb 100.00 % 47 19 19 0 1.95
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/testing/assertions.rb 76.92 % 24 13 10 3 0.77
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/testing/assertions/response.rb 89.74 % 107 39 35 4 1.64
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/testing/assertions/routing.rb 19.23 % 222 78 15 63 0.19
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/testing/integration.rb 85.64 % 652 202 173 29 1.49
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/testing/request_encoder.rb 83.33 % 55 30 25 5 1.13
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/testing/test_process.rb 60.00 % 50 20 12 8 0.60
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/testing/test_request.rb 54.29 % 71 35 19 16 0.54
target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/testing/test_response.rb 63.16 % 53 19 12 7 0.79
target/rubygems/gems/actionview-5.2.3/lib/action_view/buffers.rb 70.00 % 52 30 21 9 1.73
target/rubygems/gems/actionview-5.2.3/lib/action_view/dependency_tracker.rb 51.32 % 175 76 39 37 0.55
target/rubygems/gems/actionview-5.2.3/lib/action_view/digestor.rb 42.25 % 134 71 30 41 0.44
target/rubygems/gems/actionview-5.2.3/lib/action_view/flows.rb 47.37 % 76 38 18 20 0.47
target/rubygems/gems/actionview-5.2.3/lib/action_view/renderer/abstract_renderer.rb 95.00 % 55 20 19 1 3.50
target/rubygems/gems/actionview-5.2.3/lib/action_view/renderer/partial_renderer.rb 50.31 % 552 163 82 81 1.09
target/rubygems/gems/actionview-5.2.3/lib/action_view/renderer/partial_renderer/collection_caching.rb 36.67 % 57 30 11 19 0.37
target/rubygems/gems/actionview-5.2.3/lib/action_view/renderer/renderer.rb 78.95 % 56 19 15 4 1.00
target/rubygems/gems/actionview-5.2.3/lib/action_view/renderer/template_renderer.rb 64.29 % 102 56 36 20 0.71
target/rubygems/gems/actionview-5.2.3/lib/action_view/routing_url_for.rb 39.47 % 145 38 15 23 0.39
target/rubygems/gems/activejob-5.2.3/lib/active_job/arguments.rb 35.90 % 165 78 28 50 0.36
target/rubygems/gems/activejob-5.2.3/lib/active_job/base.rb 100.00 % 74 23 23 0 1.00
target/rubygems/gems/activejob-5.2.3/lib/active_job/callbacks.rb 87.50 % 155 24 21 3 0.92
target/rubygems/gems/activejob-5.2.3/lib/active_job/core.rb 49.02 % 158 51 25 26 0.49
target/rubygems/gems/activejob-5.2.3/lib/active_job/enqueuing.rb 45.00 % 59 20 9 11 0.45
target/rubygems/gems/activejob-5.2.3/lib/active_job/exceptions.rb 31.25 % 134 32 10 22 0.31
target/rubygems/gems/activejob-5.2.3/lib/active_job/execution.rb 52.38 % 49 21 11 10 0.52
target/rubygems/gems/activejob-5.2.3/lib/active_job/logging.rb 38.57 % 130 70 27 43 0.39
target/rubygems/gems/activejob-5.2.3/lib/active_job/queue_adapter.rb 75.00 % 60 28 21 7 0.93
target/rubygems/gems/activejob-5.2.3/lib/active_job/queue_adapters.rb 100.00 % 139 20 20 0 1.05
target/rubygems/gems/activejob-5.2.3/lib/active_job/queue_adapters/async_adapter.rb 59.09 % 116 44 26 18 0.68
target/rubygems/gems/activejob-5.2.3/lib/active_job/queue_name.rb 66.67 % 49 21 14 7 0.67
target/rubygems/gems/activejob-5.2.3/lib/active_job/queue_priority.rb 60.00 % 43 15 9 6 0.60
target/rubygems/gems/activejob-5.2.3/lib/active_job/test_helper.rb 27.87 % 456 122 34 88 0.28
target/rubygems/gems/activejob-5.2.3/lib/active_job/translation.rb 83.33 % 13 6 5 1 0.83
target/rubygems/gems/activerecord-5.2.3/lib/active_record/association_relation.rb 52.38 % 40 21 11 10 0.52
target/rubygems/gems/activerecord-5.2.3/lib/active_record/associations/collection_proxy.rb 47.42 % 1131 97 46 51 0.48
target/rubygems/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/transaction.rb 55.77 % 283 156 87 69 0.82
target/rubygems/gems/activerecord-5.2.3/lib/active_record/fixture_set/file.rb 39.53 % 82 43 17 26 0.40
target/rubygems/gems/activerecord-5.2.3/lib/active_record/fixtures.rb 44.51 % 1065 328 146 182 0.56
target/rubygems/gems/activerecord-5.2.3/lib/active_record/legacy_yaml_adapter.rb 34.78 % 48 23 8 15 0.39
target/rubygems/gems/activerecord-5.2.3/lib/active_record/railties/controller_runtime.rb 96.67 % 56 30 29 1 1.23
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation.rb 39.65 % 629 227 90 137 0.66
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/batches.rb 15.94 % 287 69 11 58 0.16
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/batches/batch_enumerator.rb 42.86 % 69 21 9 12 0.52
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/calculations.rb 20.13 % 417 154 31 123 0.27
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/finder_methods.rb 25.13 % 565 187 47 140 0.25
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/from_clause.rb 92.31 % 26 13 12 1 1.08
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/merger.rb 23.91 % 193 92 22 70 0.24
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder.rb 62.20 % 152 82 51 31 0.93
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/array_handler.rb 38.46 % 48 26 10 16 0.50
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/association_query_value.rb 50.00 % 46 22 11 11 0.50
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/base_handler.rb 88.89 % 19 9 8 1 1.00
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/basic_object_handler.rb 100.00 % 20 10 10 0 1.10
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb 46.15 % 56 26 12 14 0.46
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/range_handler.rb 47.62 % 42 21 10 11 0.52
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/relation_handler.rb 44.44 % 19 9 4 5 0.44
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/query_attribute.rb 69.57 % 45 23 16 7 0.96
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/query_methods.rb 45.78 % 1231 391 179 212 1.29
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/spawn_methods.rb 48.15 % 77 27 13 14 0.67
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/where_clause.rb 40.86 % 186 93 38 55 0.45
target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/where_clause_factory.rb 82.35 % 34 17 14 3 0.82
target/rubygems/gems/activerecord-5.2.3/lib/active_record/result.rb 85.71 % 149 56 48 8 2.68
target/rubygems/gems/activerecord-5.2.3/lib/active_record/runtime_registry.rb 100.00 % 24 8 8 0 1.25
target/rubygems/gems/activerecord-5.2.3/lib/active_record/schema_migration.rb 65.38 % 56 26 17 9 0.85
target/rubygems/gems/activerecord-5.2.3/lib/active_record/statement_cache.rb 83.02 % 121 53 44 9 1.08
target/rubygems/gems/activerecord-5.2.3/lib/active_record/table_metadata.rb 67.44 % 82 43 29 14 0.79
target/rubygems/gems/activestorage-5.2.3/lib/active_storage/attached.rb 63.16 % 40 19 12 7 0.63
target/rubygems/gems/activestorage-5.2.3/lib/active_storage/attached/macros.rb 12.50 % 110 32 4 28 0.13
target/rubygems/gems/activestorage-5.2.3/lib/active_storage/attached/many.rb 50.00 % 59 14 7 7 0.50
target/rubygems/gems/activestorage-5.2.3/lib/active_storage/attached/one.rb 40.63 % 82 32 13 19 0.41
target/rubygems/gems/activesupport-5.2.3/lib/active_support/all.rb 100.00 % 5 3 3 0 1.00
target/rubygems/gems/activesupport-5.2.3/lib/active_support/backtrace_cleaner.rb 53.33 % 105 30 16 14 0.63
target/rubygems/gems/activesupport-5.2.3/lib/active_support/cache/memory_store.rb 32.97 % 169 91 30 61 0.33
target/rubygems/gems/activesupport-5.2.3/lib/active_support/cache/null_store.rb 86.67 % 43 15 13 2 0.87
target/rubygems/gems/activesupport-5.2.3/lib/active_support/cache/strategy/local_cache.rb 44.66 % 194 103 46 57 0.54
target/rubygems/gems/activesupport-5.2.3/lib/active_support/cache/strategy/local_cache_middleware.rb 95.83 % 45 24 23 1 1.25
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext.rb 100.00 % 5 2 2 0 12.00
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/big_decimal.rb 100.00 % 3 1 1 0 1.00
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/digest/uuid.rb 44.00 % 53 25 11 14 0.44
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/file.rb 100.00 % 3 1 1 0 1.00
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/file/atomic.rb 17.39 % 70 23 4 19 0.17
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/hash.rb 100.00 % 11 9 9 0 1.00
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/integer.rb 100.00 % 5 3 3 0 1.00
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/integer/inflections.rb 66.67 % 31 6 4 2 0.67
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/integer/multiple.rb 66.67 % 12 3 2 1 0.67
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/kernel.rb 100.00 % 6 4 4 0 1.00
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/kernel/agnostics.rb 50.00 % 13 4 2 2 0.50
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/kernel/concern.rb 80.00 % 14 5 4 1 0.80
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/marshal.rb 45.45 % 24 11 5 6 0.45
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/numeric.rb 100.00 % 6 4 4 0 1.00
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/numeric/conversions.rb 47.83 % 140 23 11 12 9.17
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/numeric/inquiry.rb 11.11 % 28 9 1 8 0.11
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/securerandom.rb 50.00 % 25 8 4 4 0.50
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/string.rb 100.00 % 15 13 13 0 1.00
target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/string/exclude.rb 66.67 % 13 3 2 1 0.67
target/rubygems/gems/activesupport-5.2.3/lib/active_support/current_attributes.rb 47.06 % 195 51 24 27 0.59
target/rubygems/gems/activesupport-5.2.3/lib/active_support/digest.rb 80.00 % 20 10 8 2 0.80
target/rubygems/gems/activesupport-5.2.3/lib/active_support/test_case.rb 97.62 % 72 42 41 1 1.14
target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/assertions.rb 18.52 % 214 54 10 44 0.19
target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/autorun.rb 100.00 % 7 3 3 0 1.00
target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/constant_lookup.rb 53.33 % 51 15 8 7 0.53
target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/declarative.rb 83.33 % 28 12 10 2 1.25
target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/deprecation.rb 32.00 % 39 25 8 17 0.32
target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/file_fixtures.rb 58.33 % 36 12 7 5 0.58
target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/isolation.rb 22.95 % 110 61 14 47 0.23
target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/setup_and_teardown.rb 95.24 % 55 21 20 1 1.48
target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/stream.rb 25.93 % 44 27 7 20 0.26
target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/tagged_logging.rb 100.00 % 27 15 15 0 2.07
target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/time_helpers.rb 50.88 % 200 57 29 28 0.63
target/rubygems/gems/devise-4.7.1/lib/devise/controllers/scoped_views.rb 77.78 % 19 9 7 2 0.78
target/rubygems/gems/devise-4.7.1/lib/devise/controllers/url_helpers.rb 79.17 % 69 24 19 5 12.83
target/rubygems/gems/devise-4.7.1/lib/devise/delegator.rb 50.00 % 18 8 4 4 0.50
target/rubygems/gems/devise-4.7.1/lib/devise/failure_app.rb 30.22 % 285 139 42 97 0.30
target/rubygems/gems/devise-4.7.1/lib/devise/hooks/forgetable.rb 33.33 % 11 3 1 2 0.33
target/rubygems/gems/devise-4.7.1/lib/devise/hooks/rememberable.rb 80.00 % 9 5 4 1 1.20
target/rubygems/gems/devise-4.7.1/lib/devise/models/database_authenticatable.rb 41.67 % 237 84 35 49 0.43
target/rubygems/gems/devise-4.7.1/lib/devise/models/recoverable.rb 33.33 % 167 63 21 42 0.33
target/rubygems/gems/devise-4.7.1/lib/devise/models/registerable.rb 80.00 % 29 10 8 2 0.80
target/rubygems/gems/devise-4.7.1/lib/devise/models/rememberable.rb 43.64 % 158 55 24 31 0.44
target/rubygems/gems/devise-4.7.1/lib/devise/models/validatable.rb 77.42 % 73 31 24 7 0.94
target/rubygems/gems/devise-4.7.1/lib/devise/orm/active_record.rb 100.00 % 7 3 3 0 1.00
target/rubygems/gems/devise-4.7.1/lib/devise/secret_key_finder.rb 76.92 % 27 13 10 3 0.77
target/rubygems/gems/devise-4.7.1/lib/devise/strategies/authenticatable.rb 41.43 % 178 70 29 41 0.41
target/rubygems/gems/devise-4.7.1/lib/devise/strategies/base.rb 50.00 % 22 10 5 5 0.50
target/rubygems/gems/devise-4.7.1/lib/devise/strategies/database_authenticatable.rb 40.00 % 31 15 6 9 0.40
target/rubygems/gems/devise-4.7.1/lib/devise/strategies/rememberable.rb 46.43 % 67 28 13 15 0.46
target/rubygems/gems/devise-4.7.1/lib/devise/token_generator.rb 58.82 % 32 17 10 7 0.59
target/rubygems/gems/globalid-0.4.2/lib/global_id/identification.rb 71.43 % 25 14 10 4 0.71
target/rubygems/gems/globalid-0.4.2/lib/global_id/signed_global_id.rb 53.33 % 85 45 24 21 0.53
target/rubygems/gems/globalid-0.4.2/lib/global_id/verifier.rb 77.78 % 15 9 7 2 0.78
target/rubygems/gems/i18n-1.6.0/lib/i18n/backend.rb 100.00 % 21 17 17 0 1.00
target/rubygems/gems/i18n-1.6.0/lib/i18n/backend/base.rb 25.56 % 284 133 34 99 0.29
target/rubygems/gems/i18n-1.6.0/lib/i18n/backend/simple.rb 43.75 % 111 48 21 27 0.60
target/rubygems/gems/i18n-1.6.0/lib/i18n/backend/transliterator.rb 42.11 % 108 38 16 22 0.42
target/rubygems/gems/i18n-1.6.0/lib/i18n/core_ext/hash.rb 44.00 % 47 25 11 14 0.44
target/rubygems/gems/jbuilder-2.9.1/lib/jbuilder/dependency_tracker.rb 75.00 % 61 24 18 6 0.75
target/rubygems/gems/jdbc-postgres-42.2.6/lib/jdbc/postgres.rb 66.67 % 53 33 22 11 0.70
target/rubygems/gems/jdbc-postgres-42.2.6/lib/jdbc/postgres/version.rb 100.00 % 6 4 4 0 1.00
target/rubygems/gems/mail-2.7.1/lib/mail.rb 87.50 % 85 48 42 6 3.13
target/rubygems/gems/mail-2.7.1/lib/mail/attachments_list.rb 14.04 % 110 57 8 49 0.14
target/rubygems/gems/mail-2.7.1/lib/mail/body.rb 29.27 % 328 123 36 87 0.29
target/rubygems/gems/mail-2.7.1/lib/mail/check_delivery_params.rb 31.03 % 60 29 9 20 0.31
target/rubygems/gems/mail-2.7.1/lib/mail/configuration.rb 29.41 % 78 34 10 24 0.29
target/rubygems/gems/mail-2.7.1/lib/mail/constants.rb 100.00 % 57 46 46 0 1.00
target/rubygems/gems/mail-2.7.1/lib/mail/core_extensions/smtp.rb 9.09 % 28 11 1 10 0.09
target/rubygems/gems/mail-2.7.1/lib/mail/core_extensions/string.rb 42.86 % 17 7 3 4 0.43
target/rubygems/gems/mail-2.7.1/lib/mail/elements.rb 100.00 % 15 13 13 0 1.00
target/rubygems/gems/mail-2.7.1/lib/mail/encodings.rb 26.19 % 343 126 33 93 0.37
target/rubygems/gems/mail-2.7.1/lib/mail/encodings/7bit.rb 81.82 % 22 11 9 2 0.82
target/rubygems/gems/mail-2.7.1/lib/mail/encodings/8bit.rb 88.89 % 18 9 8 1 0.89
target/rubygems/gems/mail-2.7.1/lib/mail/encodings/base64.rb 70.59 % 38 17 12 5 0.71
target/rubygems/gems/mail-2.7.1/lib/mail/encodings/binary.rb 100.00 % 13 7 7 0 1.00
target/rubygems/gems/mail-2.7.1/lib/mail/encodings/identity.rb 70.00 % 24 10 7 3 0.70
target/rubygems/gems/mail-2.7.1/lib/mail/encodings/quoted_printable.rb 65.00 % 45 20 13 7 0.65
target/rubygems/gems/mail-2.7.1/lib/mail/encodings/transfer_encoding.rb 37.14 % 77 35 13 22 0.37
target/rubygems/gems/mail-2.7.1/lib/mail/encodings/unix_to_unix.rb 81.82 % 20 11 9 2 0.82
target/rubygems/gems/mail-2.7.1/lib/mail/envelope.rb 60.00 % 31 10 6 4 0.60
target/rubygems/gems/mail-2.7.1/lib/mail/field.rb 43.86 % 299 114 50 64 0.68
target/rubygems/gems/mail-2.7.1/lib/mail/field_list.rb 33.33 % 34 12 4 8 0.33
target/rubygems/gems/mail-2.7.1/lib/mail/fields.rb 100.00 % 36 33 33 0 1.00
target/rubygems/gems/mail-2.7.1/lib/mail/fields/bcc_field.rb 55.00 % 68 20 11 9 0.55
target/rubygems/gems/mail-2.7.1/lib/mail/fields/cc_field.rb 64.29 % 55 14 9 5 0.64
target/rubygems/gems/mail-2.7.1/lib/mail/fields/comments_field.rb 55.56 % 42 9 5 4 0.56
target/rubygems/gems/mail-2.7.1/lib/mail/fields/common/address_container.rb 57.14 % 17 7 4 3 0.57
target/rubygems/gems/mail-2.7.1/lib/mail/fields/common/common_address.rb 31.71 % 161 82 26 56 0.32
target/rubygems/gems/mail-2.7.1/lib/mail/fields/common/common_date.rb 52.94 % 36 17 9 8 0.53
target/rubygems/gems/mail-2.7.1/lib/mail/fields/common/common_field.rb 51.85 % 52 27 14 13 0.52
target/rubygems/gems/mail-2.7.1/lib/mail/fields/common/common_message_id.rb 45.83 % 49 24 11 13 0.46
target/rubygems/gems/mail-2.7.1/lib/mail/fields/common/parameter_hash.rb 20.69 % 59 29 6 23 0.21
target/rubygems/gems/mail-2.7.1/lib/mail/fields/content_description_field.rb 55.56 % 20 9 5 4 0.56
target/rubygems/gems/mail-2.7.1/lib/mail/fields/content_disposition_field.rb 34.21 % 71 38 13 25 0.34
target/rubygems/gems/mail-2.7.1/lib/mail/fields/content_id_field.rb 45.16 % 63 31 14 17 0.45
target/rubygems/gems/mail-2.7.1/lib/mail/fields/content_location_field.rb 50.00 % 43 20 10 10 0.50
target/rubygems/gems/mail-2.7.1/lib/mail/fields/content_transfer_encoding_field.rb 45.45 % 45 22 10 12 0.45
target/rubygems/gems/mail-2.7.1/lib/mail/fields/content_type_field.rb 26.26 % 197 99 26 73 0.26
target/rubygems/gems/mail-2.7.1/lib/mail/fields/date_field.rb 50.00 % 57 18 9 9 0.50
target/rubygems/gems/mail-2.7.1/lib/mail/fields/from_field.rb 64.29 % 55 14 9 5 0.64
target/rubygems/gems/mail-2.7.1/lib/mail/fields/in_reply_to_field.rb 56.25 % 57 16 9 7 0.56
target/rubygems/gems/mail-2.7.1/lib/mail/fields/keywords_field.rb 52.38 % 44 21 11 10 0.52
target/rubygems/gems/mail-2.7.1/lib/mail/fields/message_id_field.rb 50.00 % 83 28 14 14 0.50
target/rubygems/gems/mail-2.7.1/lib/mail/fields/mime_version_field.rb 46.15 % 54 26 12 14 0.46
target/rubygems/gems/mail-2.7.1/lib/mail/fields/received_field.rb 42.86 % 76 28 12 16 0.43
target/rubygems/gems/mail-2.7.1/lib/mail/fields/references_field.rb 56.25 % 57 16 9 7 0.56
target/rubygems/gems/mail-2.7.1/lib/mail/fields/reply_to_field.rb 64.29 % 55 14 9 5 0.64
target/rubygems/gems/mail-2.7.1/lib/mail/fields/resent_bcc_field.rb 64.29 % 55 14 9 5 0.64
target/rubygems/gems/mail-2.7.1/lib/mail/fields/resent_cc_field.rb 64.29 % 55 14 9 5 0.64
target/rubygems/gems/mail-2.7.1/lib/mail/fields/resent_date_field.rb 52.94 % 35 17 9 8 0.53
target/rubygems/gems/mail-2.7.1/lib/mail/fields/resent_from_field.rb 64.29 % 55 14 9 5 0.64
target/rubygems/gems/mail-2.7.1/lib/mail/fields/resent_message_id_field.rb 58.82 % 35 17 10 7 0.59
target/rubygems/gems/mail-2.7.1/lib/mail/fields/resent_sender_field.rb 61.11 % 62 18 11 7 0.61
target/rubygems/gems/mail-2.7.1/lib/mail/fields/resent_to_field.rb 64.29 % 55 14 9 5 0.64
target/rubygems/gems/mail-2.7.1/lib/mail/fields/return_path_field.rb 57.89 % 65 19 11 8 0.58
target/rubygems/gems/mail-2.7.1/lib/mail/fields/sender_field.rb 60.00 % 67 20 12 8 0.60
target/rubygems/gems/mail-2.7.1/lib/mail/fields/structured_field.rb 55.56 % 52 18 10 8 0.56
target/rubygems/gems/mail-2.7.1/lib/mail/fields/subject_field.rb 71.43 % 17 7 5 2 0.71
target/rubygems/gems/mail-2.7.1/lib/mail/fields/to_field.rb 64.29 % 55 14 9 5 0.64
target/rubygems/gems/mail-2.7.1/lib/mail/fields/unstructured_field.rb 21.65 % 222 97 21 76 0.22
target/rubygems/gems/mail-2.7.1/lib/mail/header.rb 35.48 % 278 93 33 60 0.35
target/rubygems/gems/mail-2.7.1/lib/mail/indifferent_hash.rb 53.57 % 147 56 30 26 0.54
target/rubygems/gems/mail-2.7.1/lib/mail/mail.rb 51.67 % 262 60 31 29 0.52
target/rubygems/gems/mail-2.7.1/lib/mail/matchers/attachment_matchers.rb 66.67 % 29 15 10 5 0.67
target/rubygems/gems/mail-2.7.1/lib/mail/matchers/has_sent_mail.rb 32.20 % 201 118 38 80 0.32
target/rubygems/gems/mail-2.7.1/lib/mail/message.rb 30.83 % 2170 639 197 442 0.31
target/rubygems/gems/mail-2.7.1/lib/mail/multibyte.rb 61.11 % 92 18 11 7 0.61
target/rubygems/gems/mail-2.7.1/lib/mail/multibyte/chars.rb 24.85 % 476 165 41 124 0.33
target/rubygems/gems/mail-2.7.1/lib/mail/multibyte/unicode.rb 27.23 % 405 202 55 147 0.61
target/rubygems/gems/mail-2.7.1/lib/mail/multibyte/utils.rb 36.00 % 61 25 9 16 0.36
target/rubygems/gems/mail-2.7.1/lib/mail/network.rb 100.00 % 16 12 12 0 1.00
target/rubygems/gems/mail-2.7.1/lib/mail/network/delivery_methods/file_delivery.rb 50.00 % 42 16 8 8 0.50
target/rubygems/gems/mail-2.7.1/lib/mail/network/delivery_methods/sendmail.rb 44.00 % 95 25 11 14 0.44
target/rubygems/gems/mail-2.7.1/lib/mail/network/delivery_methods/smtp.rb 32.35 % 149 34 11 23 0.32
target/rubygems/gems/mail-2.7.1/lib/mail/network/delivery_methods/test_mailer.rb 61.54 % 42 13 8 5 0.62
target/rubygems/gems/mail-2.7.1/lib/mail/network/retriever_methods/base.rb 30.00 % 64 20 6 14 0.30
target/rubygems/gems/mail-2.7.1/lib/mail/part.rb 41.82 % 123 55 23 32 0.42
target/rubygems/gems/mail-2.7.1/lib/mail/parts_list.rb 43.24 % 74 37 16 21 0.43
target/rubygems/gems/mail-2.7.1/lib/mail/utilities.rb 36.94 % 324 111 41 70 0.43
target/rubygems/gems/mail-2.7.1/lib/mail/version.rb 88.89 % 17 9 8 1 0.89
target/rubygems/gems/mail-2.7.1/lib/mail/version_specific/ruby_1_9.rb 26.67 % 278 135 36 99 0.27
target/rubygems/gems/mini_mime-1.0.2/lib/mini_mime.rb 37.50 % 166 96 36 60 0.38
target/rubygems/gems/mini_mime-1.0.2/lib/mini_mime/version.rb 100.00 % 3 2 2 0 1.00
target/rubygems/gems/minitest-5.11.3/lib/minitest/pride_plugin.rb 37.93 % 142 58 22 36 0.38
target/rubygems/gems/multi_json-1.13.1/lib/multi_json/adapter.rb 74.07 % 49 27 20 7 0.93
target/rubygems/gems/multi_json-1.13.1/lib/multi_json/adapters/json_common.rb 69.23 % 23 13 9 4 1.00
target/rubygems/gems/multi_json-1.13.1/lib/multi_json/adapters/json_gem.rb 100.00 % 11 6 6 0 1.00
target/rubygems/gems/rack-2.0.7/lib/rack/conditional_get.rb 54.29 % 79 35 19 16 0.69
target/rubygems/gems/rack-2.0.7/lib/rack/etag.rb 97.37 % 74 38 37 1 1.13
target/rubygems/gems/rack-2.0.7/lib/rack/file.rb 33.71 % 176 89 30 59 0.34
target/rubygems/gems/rack-2.0.7/lib/rack/head.rb 81.82 % 25 11 9 2 1.18
target/rubygems/gems/rack-2.0.7/lib/rack/lint.rb 18.41 % 760 239 44 195 0.18
target/rubygems/gems/rack-2.0.7/lib/rack/method_override.rb 57.14 % 50 28 16 12 0.68
target/rubygems/gems/rack-2.0.7/lib/rack/mime.rb 63.64 % 677 11 7 4 0.64
target/rubygems/gems/rack-2.0.7/lib/rack/mock.rb 66.67 % 196 96 64 32 1.53
target/rubygems/gems/rack-2.0.7/lib/rack/runtime.rb 100.00 % 32 16 16 0 1.38
target/rubygems/gems/rack-2.0.7/lib/rack/sendfile.rb 40.54 % 158 37 15 22 0.49
target/rubygems/gems/rack-2.0.7/lib/rack/session/cookie.rb 42.86 % 195 84 36 48 0.43
target/rubygems/gems/rack-2.0.7/lib/rack/tempfile_reaper.rb 100.00 % 22 11 11 0 1.45
target/rubygems/gems/rails-dom-testing-2.0.3/lib/rails-dom-testing.rb 100.00 % 1 1 1 0 1.00
target/rubygems/gems/rails-dom-testing-2.0.3/lib/rails/dom/testing/assertions.rb 100.00 % 18 11 11 0 1.00
target/rubygems/gems/rails-dom-testing-2.0.3/lib/rails/dom/testing/assertions/dom_assertions.rb 38.89 % 76 36 14 22 0.39
target/rubygems/gems/rails-dom-testing-2.0.3/lib/rails/dom/testing/assertions/selector_assertions.rb 30.51 % 302 59 18 41 0.31
target/rubygems/gems/rails-dom-testing-2.0.3/lib/rails/dom/testing/assertions/selector_assertions/count_describable.rb 61.11 % 32 18 11 7 0.61
target/rubygems/gems/rails-dom-testing-2.0.3/lib/rails/dom/testing/assertions/selector_assertions/html_selector.rb 22.22 % 114 63 14 49 0.24
target/rubygems/gems/rails-dom-testing-2.0.3/lib/rails/dom/testing/assertions/selector_assertions/substitution_context.rb 47.06 % 33 17 8 9 0.47
target/rubygems/gems/railties-5.2.3/lib/minitest/rails_plugin.rb 86.67 % 63 30 26 4 1.00
target/rubygems/gems/railties-5.2.3/lib/rails/application/bootstrap.rb 80.43 % 89 46 37 9 0.80
target/rubygems/gems/railties-5.2.3/lib/rails/application/default_middleware_stack.rb 78.95 % 107 57 45 12 0.79
target/rubygems/gems/railties-5.2.3/lib/rails/application/finisher.rb 73.26 % 196 86 63 23 0.78
target/rubygems/gems/railties-5.2.3/lib/rails/application/routes_reloader.rb 100.00 % 55 31 31 0 1.26
target/rubygems/gems/railties-5.2.3/lib/rails/backtrace_cleaner.rb 100.00 % 34 23 23 0 1.22
target/rubygems/gems/railties-5.2.3/lib/rails/generators/test_case.rb 100.00 % 37 13 13 0 1.00
target/rubygems/gems/railties-5.2.3/lib/rails/generators/testing/assertions.rb 40.54 % 127 37 15 22 0.41
target/rubygems/gems/railties-5.2.3/lib/rails/generators/testing/behaviour.rb 66.67 % 111 45 30 15 0.67
target/rubygems/gems/railties-5.2.3/lib/rails/generators/testing/setup_and_teardown.rb 54.55 % 20 11 6 5 0.55
target/rubygems/gems/railties-5.2.3/lib/rails/rack/logger.rb 84.09 % 80 44 37 7 1.41
target/rubygems/gems/railties-5.2.3/lib/rails/test_help.rb 84.62 % 48 26 22 4 0.92
target/rubygems/gems/railties-5.2.3/lib/rails/test_unit/reporter.rb 48.39 % 110 62 30 32 0.66
target/rubygems/gems/responders-3.0.0/lib/responders/flash_responder.rb 27.54 % 206 69 19 50 0.28
target/rubygems/gems/sprockets-3.7.2/lib/sprockets/autoload/sass.rb 100.00 % 7 4 4 0 1.00
target/rubygems/gems/sprockets-3.7.2/lib/sprockets/cache/file_store.rb 31.65 % 186 79 25 54 0.32
target/rubygems/gems/thor-0.20.3/lib/thor/shell/basic.rb 20.83 % 482 216 45 171 0.21
target/rubygems/gems/tzinfo-data-1.2019.2/lib/tzinfo/data/definitions/Etc/UTC.rb 100.00 % 21 8 8 0 1.00

Ungrouped ( 70.59% covered at 0.71 hits/line )

1 files in total.
17 relevant lines, 12 lines covered and 5 lines missed. ( 70.59% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line
target/rubygems/gems/activestorage-5.2.3/config/routes.rb 70.59 % 31 17 12 5 0.71

app/channels/application_cable/channel.rb

0.0% lines covered

4 relevant lines. 0 lines covered and 4 lines missed.
    
  1. module ApplicationCable
  2. class Channel < ActionCable::Channel::Base
  3. end
  4. end

app/channels/application_cable/connection.rb

0.0% lines covered

4 relevant lines. 0 lines covered and 4 lines missed.
    
  1. module ApplicationCable
  2. class Connection < ActionCable::Connection::Base
  3. end
  4. end

app/controllers/application_controller.rb

81.82% lines covered

11 relevant lines. 9 lines covered and 2 lines missed.
    
  1. 1 class ApplicationController < ActionController::Base
  2. 1 include RouteHelper
  3. 1 before_action :setup_gon
  4. 1 rescue_from Exception, java.lang.Throwable, :with => :internal_error
  5. 1 def internal_error(exception)
  6. $log.error{LEX("An unhandled exception occurred!", exception)}
  7. raise exception
  8. end
  9. 1 def setup_gon
  10. 2 gon.routes = setup_routes
  11. 2 gon.packs = packed_assets
  12. 2 gon.user = ""
  13. end
  14. end

app/controllers/concerns/route_helper.rb

92.16% lines covered

51 relevant lines. 47 lines covered and 4 lines missed.
    
  1. 1 module RouteHelper
  2. 1 include Webpacker::Helper
  3. 1 include ActionView::Helpers::AssetUrlHelper
  4. 1 IMAGE_EXTENSIONS = %w(.jpeg .jpg .png .gif).freeze
  5. 1 IMAGE_ROOT_PATH = 'media/packs/images/'
  6. 1 def setup_routes
  7. 2 original_verbosity = $VERBOSE
  8. 2 $VERBOSE = nil
  9. 2 routes = Rails.application.routes.named_routes.helper_names - ['rails_blob_path', 'rails_blob_url', 'rails_representation_path', 'rails_representation_url']
  10. 2 $VERBOSE = original_verbosity
  11. 2 @@routes_hash ||= {}
  12. 2 if @@routes_hash.empty?
  13. 1 routes.each do |route|
  14. 48 begin
  15. 48 @@routes_hash[route] = self.send(route)
  16. rescue ActionController::UrlGenerationError => ex
  17. 8 if (ex.message =~ /missing required keys: \[(.*?)\]/)
  18. 8 keys = $1
  19. 8 keys = keys.split(',')
  20. 8 keys.map! do |e|
  21. 16 e.gsub!(':', '')
  22. 16 e.strip
  23. end
  24. 8 required_keys_hash = {}
  25. 8 keys.each do |key|
  26. 16 required_keys_hash[key.to_sym] = ':' + key.to_s
  27. end
  28. 8 @@routes_hash[route] = self.send(route, required_keys_hash)
  29. else
  30. raise ex
  31. end
  32. end
  33. end
  34. end
  35. #$log.debug('routes hash passed to javascript is ' + @@routes_hash.to_s)
  36. 2 @@routes_hash
  37. end
  38. 1 def setup_packed_assets
  39. if Webpacker.instance.config.cache_manifest?
  40. @@packed_assets ||= packed_assets
  41. else
  42. @@packed_assets = packed_assets
  43. end
  44. end
  45. 1 private
  46. 1 def packed_assets
  47. 2 h = {}
  48. 2 h[:paths] = {}
  49. 2 h[:urls] = {}
  50. 2 h[:urls][:images] = {}
  51. 2 h[:paths][:images] = {}
  52. 2 Webpacker.instance.manifest.refresh.each_pair do |k,v|
  53. 1040 unless k =~ /map$|entrypoints$/
  54. 856 url = asset_pack_url k
  55. 856 path = asset_pack_path k
  56. 856 h[:urls][k] = url
  57. 856 h[:paths][k] = path
  58. 856 if IMAGE_EXTENSIONS.include?(File.extname k)
  59. 44 rootless = k.sub(IMAGE_ROOT_PATH,'')
  60. 44 h[:urls][:images][k] = url
  61. 44 h[:urls][:images][rootless] = url
  62. 44 h[:paths][:images][k] = path
  63. 44 h[:paths][:images][rootless] = path
  64. end
  65. end
  66. end
  67. 2 h
  68. end
  69. end
  70. =begin
  71. Webpacker.instance.manifest
  72. Webpacker.instance.manifest.refresh #gives hash
  73. include Webpacker::Helper
  74. include ActionView::Helpers::AssetUrlHelper
  75. include ActionView::Helpers::AssetTagHelper
  76. Webpacker.instance.config.cache_manifest?
  77. =end

app/controllers/layouts_controller.rb

0.0% lines covered

8 relevant lines. 0 lines covered and 8 lines missed.
    
  1. class LayoutsController < ApplicationController
  2. # this is the root route for the application
  3. def root
  4. end
  5. def form_inputs
  6. $log.always("Received " + params.inspect)
  7. render json: {recieved: params}
  8. end
  9. end

app/controllers/react_component_controller.rb

80.0% lines covered

15 relevant lines. 12 lines covered and 3 lines missed.
    
  1. 1 class ReactComponentController < ApplicationController
  2. 1 before_action :authenticate_user!, except: [:no_auth, :fetch_time, :only_approved_users]
  3. 1 before_action :only_approved_users, only: [:auth] #keep after authenticate_user! above.
  4. #hit on account
  5. 1 def auth
  6. end
  7. 1 def no_auth
  8. end
  9. 1 def awaiting_approval
  10. redirect_to account_path if (current_user.approved)
  11. end
  12. 1 def fetch_time
  13. render json: {current_time: Time.now}
  14. end
  15. 1 private
  16. 1 def only_approved_users
  17. 2 if (current_user.approved)
  18. 1 if (!request.path.to_s.eql?(account_path.to_s))
  19. redirect_to account_path
  20. end
  21. else
  22. 1 redirect_to awaiting_approval_path
  23. end
  24. end
  25. end

app/helpers/application_helper.rb

100.0% lines covered

3 relevant lines. 3 lines covered and 0 lines missed.
    
  1. 1 module ApplicationHelper
  2. # helper method used with the asset_pack_path for resolving image paths
  3. # for example: <img style="vertical-align: middle" src="<%= asset_pack_path image('va-logo-white.png') %>" alt="VA Header Image"/>
  4. 1 def image(img)
  5. 1 RouteHelper::IMAGE_ROOT_PATH + img
  6. end
  7. end

app/jobs/application_job.rb

0.0% lines covered

2 relevant lines. 0 lines covered and 2 lines missed.
    
  1. class ApplicationJob < ActiveJob::Base
  2. end

app/mailers/application_mailer.rb

0.0% lines covered

4 relevant lines. 0 lines covered and 4 lines missed.
    
  1. class ApplicationMailer < ActionMailer::Base
  2. default from: 'from@example.com'
  3. layout 'mailer'
  4. end

app/models/application_record.rb

100.0% lines covered

2 relevant lines. 2 lines covered and 0 lines missed.
    
  1. 1 class ApplicationRecord < ActiveRecord::Base
  2. 1 self.abstract_class = true
  3. end

app/models/user.rb

100.0% lines covered

2 relevant lines. 2 lines covered and 0 lines missed.
    
  1. 1 class User < ApplicationRecord
  2. # Include default devise modules. Others available are:
  3. # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  4. 1 devise :database_authenticatable, :registerable,
  5. :recoverable, :rememberable, :validatable
  6. end

lib/logging/logging.rb

57.14% lines covered

77 relevant lines. 44 lines covered and 33 lines missed.
    
  1. 1 require 'logging'
  2. 1 require 'fileutils'
  3. 1 require 'socket'
  4. # if nil we are in trinidad
  5. 1 CATALINA_HOME = java.lang.System.properties['catalina.home']
  6. 1 subdir = (File.open('../context.txt').read.reverse.chop.reverse + '/') rescue ''
  7. 1 LOG_HOME = CATALINA_HOME.nil? ? "#{Rails.root}/logs/#{subdir}" : "#{CATALINA_HOME}/logs/#{subdir}"
  8. 1 FileUtils::mkdir_p LOG_HOME
  9. 1 Logging.basepath = Rails.root.to_s
  10. 1 module Logging
  11. #add a level here if needed....
  12. 1 RAILS_COMMON_LEVELS = [:trace, :debug, :info, :warn, :error, :fatal, :unknown, :always]
  13. 1 def self.trace(exception)
  14. trace_str = "\n"
  15. if exception.respond_to? :backtrace
  16. trace_str << exception.to_s << "\n"
  17. unless exception.backtrace.nil?
  18. trace_str << exception.backtrace.join("\n")
  19. end
  20. end
  21. end
  22. end
  23. #Logging.caller_tracing=true
  24. 1 Logging.init *Logging::RAILS_COMMON_LEVELS
  25. 1 Logging.color_scheme('pretty',
  26. levels: {
  27. :info => :green,
  28. :warn => :yellow,
  29. :error => :red,
  30. :fatal => [:white, :on_red],
  31. :unknown => [:yellow, :on_blue],
  32. :always => :white
  33. },
  34. date: :yellow,
  35. #logger: :cyan,
  36. #message: :magenta,
  37. file: :magenta,
  38. line: :cyan
  39. )
  40. 1 color_scheme = WINDOWS ? 'pretty' : :default
  41. #move pattern to prop file
  42. 1 pattern = $PROPS['LOG.pattern']
  43. 2 Logging.appenders.stdout(
  44. 'stdout',
  45. :layout => Logging.layouts.pattern(
  46. :pattern => pattern,
  47. :color_scheme => color_scheme
  48. )
  49. )
  50. 2 rf = Logging.appenders.rolling_file(
  51. 'file',
  52. layout: Logging.layouts.pattern(
  53. pattern: pattern,
  54. color_scheme: color_scheme,
  55. # backtrace: true
  56. ),
  57. roll_by: $PROPS['LOG.roll_by'],
  58. keep: $PROPS['LOG.keep'].to_i,
  59. age: $PROPS['LOG.age'],
  60. filename: LOG_HOME + $PROPS['LOG.filename'],
  61. truncate: true
  62. )
  63. 2 error_appender = Logging.appenders.rolling_file(
  64. 'file',
  65. layout: Logging.layouts.pattern(
  66. pattern: pattern,
  67. color_scheme: color_scheme,
  68. ),
  69. roll_by: $PROPS['LOG.roll_by'],
  70. keep: $PROPS['LOG.keep'].to_i,
  71. age: $PROPS['LOG.age'],
  72. filename: LOG_HOME + $PROPS['LOG.filename_error'],
  73. truncate: true
  74. )
  75. 1 class ErrorFilter < ::Logging::Filter
  76. 1 def initialize
  77. 9 @levels_hash = Logging::LEVELS.invert.map do |k,v| [k, v.to_sym] end.to_h
  78. end
  79. 1 def allow(event)
  80. 1 allowed = @levels_hash[event.level].eql?(:error) || @levels_hash[event.level].eql?(:fatal)
  81. 1 allowed ? event : nil
  82. end
  83. end
  84. #error_appender.level = :error
  85. 1 error_appender.filters=ErrorFilter.new
  86. 1 begin
  87. 1 $log = ::Logging::Logger['MainLogger']
  88. 1 $log.caller_tracing=$PROPS['LOG.caller_tracing'].upcase.eql?('TRUE')
  89. 1 $log.add_appenders 'stdout' if ($PROPS['LOG.append_stdout'].upcase.eql?('TRUE'))
  90. 1 $log.add_appenders rf
  91. #$log.add_appenders error_appender
  92. 1 $log.level = $PROPS['LOG.level'].downcase.to_sym
  93. 1 unless $PROPS['LOG.filename_admin'].nil?
  94. #rf_rails is for rails logging
  95. rf_admin = Logging.appenders.rolling_file(
  96. 'file',
  97. layout: Logging.layouts.pattern(
  98. pattern: pattern,
  99. color_scheme: color_scheme,
  100. # backtrace: true
  101. ),
  102. roll_by: $PROPS['LOG.roll_by'],
  103. keep: $PROPS['LOG.keep'].to_i,
  104. age: $PROPS['LOG.age'],
  105. filename: LOG_HOME + $PROPS['LOG.filename_admin'],
  106. truncate: true
  107. )
  108. $alog = ::Logging::Logger['LogAdmin']
  109. $alog.caller_tracing=$PROPS['LOG.caller_tracing'].upcase.eql?('TRUE')
  110. $alog.add_appenders 'stdout' if $PROPS['LOG.append_stdout'].upcase.eql?('TRUE')
  111. $alog.add_appenders rf_admin
  112. $alog.level = $PROPS['LOG.level'].downcase.to_sym
  113. end
  114. 1 ALL_LOGGERS = [$log, $alog].reject(&:nil?).freeze
  115. # these log messages will be nicely colored
  116. # the level will be colored differently for each message
  117. # PrismeLogEvent not visible yet
  118. 1 unless (File.basename($0) == 'rake')
  119. ALL_LOGGERS.each {|e| e.always 'Logging started!'}
  120. end
  121. rescue => ex
  122. warn "Logger failed to initialize. Reason is #{ex.to_s}"
  123. warn ex.backtrace.join("\n")
  124. warn 'Shutting down the web server!'
  125. java.lang.System.exit(1)
  126. end
  127. 1 ALL_LOGGERS.each do |logger|
  128. 1 logger.add_appenders error_appender
  129. end
  130. #WARNING, using these methods doesn't produce the correct file location in the logs.
  131. 1 ALL_LOGGERS.each do |logger|
  132. 1 Logging::RAILS_COMMON_LEVELS.each do |level|
  133. 8 method_name = ("#{level}_e").to_sym
  134. 8 logger.define_singleton_method(method_name) do |message, exception|
  135. logger.send(level, message.to_s)
  136. if exception.respond_to? :backtrace
  137. logger.send(level, exception.to_s)
  138. logger.send(level, exception.backtrace.join("\n")) unless exception.backtrace.nil?
  139. end
  140. end
  141. end
  142. end
  143. 1 module Kernel
  144. 1 def LEX(message, exception)
  145. result = "#{message}\n#{exception.class}: #{exception.message}\n#{exception.backtrace&.join("\n")}" rescue message.to_s
  146. result
  147. end
  148. 1 module_function :LEX
  149. end
  150. 1 $log.always "Using color scheme #{color_scheme}, Rails mode is #{Rails.env}"
  151. =begin
  152. load('lib/logging/logging.rb')
  153. $log.debug{LEX("I had a boo boo 2", ex)}
  154. =end

lib/props/prop_loader.rb

83.72% lines covered

43 relevant lines. 36 lines covered and 7 lines missed.
    
  1. 1 require 'erb'
  2. 1 module PropLoader
  3. 1 extend self
  4. 1 class << self
  5. 1 attr_accessor :props
  6. end
  7. 1 def self.load_prop_files(*dirs)
  8. 1 @props = {}
  9. 1 dirs.each do |dir|
  10. # iterate over all of the .properties files in the directory
  11. 1 Dir.glob("#{dir}/*.properties*") do |file|
  12. 1 key_prefix = File.basename(file).split(".")[0].upcase
  13. 1 if File.extname(file).eql?('.erb')
  14. 1 props = self.read_props_from_erb(file, key_prefix)
  15. else
  16. # read the file line by line stripping out properties
  17. props = read_prop_file(file, key_prefix)
  18. end
  19. 1 @props.merge!(props)
  20. end
  21. end
  22. end
  23. 1 def self.reload
  24. 1 PropLoader.load_prop_files('./config/props')
  25. 1 $PROPS = PropLoader.props.clone
  26. 1 $PROPS.freeze
  27. end
  28. 1 private
  29. 1 def self.read_prop_file(file, key_prefix)
  30. ret = {}
  31. File.readlines(file).each do |line|
  32. r = read_prop_line(line, key_prefix)
  33. ret.merge!(r)
  34. end
  35. ret
  36. end
  37. 1 def self.read_props_from_erb(erb, key_prefix)
  38. 2 props = ERB.new(File.open(erb, 'r') { |file| file.read }).result
  39. 1 properties = {}
  40. 1 prop_array = props.split("\n")
  41. 1 prop_array.each do |line|
  42. 28 properties.merge!(read_prop_line(line, key_prefix))
  43. end
  44. 1 properties
  45. end
  46. 1 def self.read_prop_line(line, key_prefix)
  47. 28 properties = {}
  48. 28 line.strip!
  49. 28 return properties if line.eql?("")
  50. 22 if (line[0] != ?# and line[0] != ?=)
  51. 11 i = line.index('=')
  52. 11 if (i)
  53. 11 properties["#{key_prefix}." + line[0..i - 1].strip] = line[i + 1..-1].strip
  54. else
  55. properties["#{key_prefix}."+line] = ''
  56. end
  57. end
  58. 22 properties
  59. end
  60. end
  61. 1 PropLoader.reload

lib/utilities.rb

0.0% lines covered

82 relevant lines. 0 lines covered and 82 lines missed.
    
  1. require 'uri'
  2. module Utilities
  3. TMP_FILE_PREFIX = './tmp/'
  4. YML_EXT = '.yml'
  5. MAVEN_TARGET_DIRECTORY = './target'
  6. ##
  7. # this method takes a camel cased word and changes it to snake case
  8. # Example: RailsKomet -> rails_komet
  9. #
  10. def to_snake_case(camel_cased_word)
  11. camel_cased_word.to_s.gsub(/::/, '/').
  12. gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').
  13. gsub(/([a-z\d])([A-Z])/, '\1_\2').
  14. tr('-', '_').
  15. downcase
  16. end
  17. ##
  18. # writes the json data to a tmp file based on the filename passed
  19. # @param json - the JSON data to write out
  20. # @param file_name - the filename to write out to the /tmp directory
  21. def json_to_yaml_file(json, file_name)
  22. if Rails.env.development?
  23. prefix = '#Fixture created on ' + Time.now.strftime('%F %H:%M:%S') + "\n"
  24. File.write("#{TMP_FILE_PREFIX}#{file_name}" + YML_EXT, prefix + json.to_yaml)
  25. #$log.trace("Writing yaml file #{TMP_FILE_PREFIX}#{file_name}.yml.")
  26. else
  27. #$log.trace("Not writing yaml file #{TMP_FILE_PREFIX}#{file_name}.yml. Rails.env = #{Rails.env}")
  28. end
  29. end
  30. ##
  31. # Convert the URL to a string for use with the json_to_yaml_file method call
  32. # @param url - the URL path to convert to a string with underscores
  33. # @return - the filename based on the URL passed
  34. def url_to_path_string(url)
  35. url = url.clone
  36. begin
  37. url.gsub!('{', '') #reduce paths like http://www.google.com/foo/{id}/faa to http://www.google.com/foo/id/faa
  38. url.gsub!('}', '') #reduce paths like http://www.google.com/foo/{id}/faa to http://www.google.com/foo/id/faa
  39. path = URI(url).path.gsub('/', '_')
  40. path = 'no_path' if path.empty?
  41. return path
  42. rescue => ex
  43. #$log.error('An invalid matched_url was given!')
  44. #$log.error(ex)
  45. end
  46. 'bad_url'
  47. end
  48. end
  49. module Kernel
  50. TRUE_VALS = %w(true t yes y on 1)
  51. FALSE_VALS = %w(false f no n off 0)
  52. def boolean(boolean_string)
  53. val = boolean_string.to_s.downcase.gsub(/\s+/, '')
  54. return false if val.empty?
  55. return true if TRUE_VALS.include?(val)
  56. return false if FALSE_VALS.include?(val)
  57. raise ArgumentError.new("invalid value for Boolean: \"#{val}\"")
  58. end
  59. def gov
  60. Java::Gov
  61. end
  62. end
  63. class String
  64. # similar to the camelize in rails, but it only mutates the first character after the underscore
  65. # Suppose you have the java method 'getInterfaceEngineURL', note how the last set of characters are all uppercase
  66. # "interface_engine_URL".camelize() => "InterfaceEngineUrl"
  67. # "interface_engine_URL".camelize_preserving => "InterfaceEngineURL"
  68. # "interface_engine_URL".camelize_preserving(false) => "interfaceEngineURL"
  69. def camelize_preserving(modify_first_letter = true)
  70. return self.split('_').each_with_index.collect do |e, i|
  71. if ((i == 0) && !modify_first_letter)
  72. else
  73. e[0] = e[0].capitalize
  74. end
  75. e
  76. end.join
  77. end
  78. def to_b
  79. boolean(self)
  80. end
  81. def os_path!
  82. self.gsub!('/', java.io.File::separator)
  83. self.gsub!('\\', java.io.File::separator)
  84. end
  85. end
  86. module JSON
  87. class << self
  88. def indifferent_parse(source, opts = {})
  89. HashWithIndifferentAccess.new(JSON.parse(source, opts))
  90. end
  91. end
  92. end
  93. module FileHelper
  94. def FileHelper.file_as_string(file)
  95. rVal = ''
  96. File.open(file, 'r') do |file_handle|
  97. file_handle.read.each_line do |line|
  98. rVal << line
  99. end
  100. end
  101. rVal
  102. end
  103. end

target/rubygems/gems/actioncable-5.2.3/lib/action_cable/channel.rb

100.0% lines covered

10 relevant lines. 10 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActionCable
  3. 1 module Channel
  4. 1 extend ActiveSupport::Autoload
  5. 1 eager_autoload do
  6. 1 autoload :Base
  7. 1 autoload :Broadcasting
  8. 1 autoload :Callbacks
  9. 1 autoload :Naming
  10. 1 autoload :PeriodicTimers
  11. 1 autoload :Streams
  12. end
  13. end
  14. end

target/rubygems/gems/actioncable-5.2.3/lib/action_cable/channel/base.rb

41.41% lines covered

99 relevant lines. 41 lines covered and 58 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "set"
  3. 1 module ActionCable
  4. 1 module Channel
  5. # The channel provides the basic structure of grouping behavior into logical units when communicating over the WebSocket connection.
  6. # You can think of a channel like a form of controller, but one that's capable of pushing content to the subscriber in addition to simply
  7. # responding to the subscriber's direct requests.
  8. #
  9. # Channel instances are long-lived. A channel object will be instantiated when the cable consumer becomes a subscriber, and then
  10. # lives until the consumer disconnects. This may be seconds, minutes, hours, or even days. That means you have to take special care
  11. # not to do anything silly in a channel that would balloon its memory footprint or whatever. The references are forever, so they won't be released
  12. # as is normally the case with a controller instance that gets thrown away after every request.
  13. #
  14. # Long-lived channels (and connections) also mean you're responsible for ensuring that the data is fresh. If you hold a reference to a user
  15. # record, but the name is changed while that reference is held, you may be sending stale data if you don't take precautions to avoid it.
  16. #
  17. # The upside of long-lived channel instances is that you can use instance variables to keep reference to objects that future subscriber requests
  18. # can interact with. Here's a quick example:
  19. #
  20. # class ChatChannel < ApplicationCable::Channel
  21. # def subscribed
  22. # @room = Chat::Room[params[:room_number]]
  23. # end
  24. #
  25. # def speak(data)
  26. # @room.speak data, user: current_user
  27. # end
  28. # end
  29. #
  30. # The #speak action simply uses the Chat::Room object that was created when the channel was first subscribed to by the consumer when that
  31. # subscriber wants to say something in the room.
  32. #
  33. # == Action processing
  34. #
  35. # Unlike subclasses of ActionController::Base, channels do not follow a RESTful
  36. # constraint form for their actions. Instead, Action Cable operates through a
  37. # remote-procedure call model. You can declare any public method on the
  38. # channel (optionally taking a <tt>data</tt> argument), and this method is
  39. # automatically exposed as callable to the client.
  40. #
  41. # Example:
  42. #
  43. # class AppearanceChannel < ApplicationCable::Channel
  44. # def subscribed
  45. # @connection_token = generate_connection_token
  46. # end
  47. #
  48. # def unsubscribed
  49. # current_user.disappear @connection_token
  50. # end
  51. #
  52. # def appear(data)
  53. # current_user.appear @connection_token, on: data['appearing_on']
  54. # end
  55. #
  56. # def away
  57. # current_user.away @connection_token
  58. # end
  59. #
  60. # private
  61. # def generate_connection_token
  62. # SecureRandom.hex(36)
  63. # end
  64. # end
  65. #
  66. # In this example, the subscribed and unsubscribed methods are not callable methods, as they
  67. # were already declared in ActionCable::Channel::Base, but <tt>#appear</tt>
  68. # and <tt>#away</tt> are. <tt>#generate_connection_token</tt> is also not
  69. # callable, since it's a private method. You'll see that appear accepts a data
  70. # parameter, which it then uses as part of its model call. <tt>#away</tt>
  71. # does not, since it's simply a trigger action.
  72. #
  73. # Also note that in this example, <tt>current_user</tt> is available because
  74. # it was marked as an identifying attribute on the connection. All such
  75. # identifiers will automatically create a delegation method of the same name
  76. # on the channel instance.
  77. #
  78. # == Rejecting subscription requests
  79. #
  80. # A channel can reject a subscription request in the #subscribed callback by
  81. # invoking the #reject method:
  82. #
  83. # class ChatChannel < ApplicationCable::Channel
  84. # def subscribed
  85. # @room = Chat::Room[params[:room_number]]
  86. # reject unless current_user.can_access?(@room)
  87. # end
  88. # end
  89. #
  90. # In this example, the subscription will be rejected if the
  91. # <tt>current_user</tt> does not have access to the chat room. On the
  92. # client-side, the <tt>Channel#rejected</tt> callback will get invoked when
  93. # the server rejects the subscription request.
  94. 1 class Base
  95. 1 include Callbacks
  96. 1 include PeriodicTimers
  97. 1 include Streams
  98. 1 include Naming
  99. 1 include Broadcasting
  100. 1 attr_reader :params, :connection, :identifier
  101. 1 delegate :logger, to: :connection
  102. 1 class << self
  103. # A list of method names that should be considered actions. This
  104. # includes all public instance methods on a channel, less
  105. # any internal methods (defined on Base), adding back in
  106. # any methods that are internal, but still exist on the class
  107. # itself.
  108. #
  109. # ==== Returns
  110. # * <tt>Set</tt> - A set of all methods that should be considered actions.
  111. 1 def action_methods
  112. @action_methods ||= begin
  113. # All public instance methods of this class, including ancestors
  114. methods = (public_instance_methods(true) -
  115. # Except for public instance methods of Base and its ancestors
  116. ActionCable::Channel::Base.public_instance_methods(true) +
  117. # Be sure to include shadowed public instance methods of this class
  118. public_instance_methods(false)).uniq.map(&:to_s)
  119. methods.to_set
  120. end
  121. end
  122. 1 private
  123. # action_methods are cached and there is sometimes need to refresh
  124. # them. ::clear_action_methods! allows you to do that, so next time
  125. # you run action_methods, they will be recalculated.
  126. 1 def clear_action_methods! # :doc:
  127. 21 @action_methods = nil
  128. end
  129. # Refresh the cached action_methods when a new action_method is added.
  130. 1 def method_added(name) # :doc:
  131. 21 super
  132. 21 clear_action_methods!
  133. end
  134. end
  135. 1 def initialize(connection, identifier, params = {})
  136. @connection = connection
  137. @identifier = identifier
  138. @params = params
  139. # When a channel is streaming via pubsub, we want to delay the confirmation
  140. # transmission until pubsub subscription is confirmed.
  141. #
  142. # The counter starts at 1 because it's awaiting a call to #subscribe_to_channel
  143. @defer_subscription_confirmation_counter = Concurrent::AtomicFixnum.new(1)
  144. @reject_subscription = nil
  145. @subscription_confirmation_sent = nil
  146. delegate_connection_identifiers
  147. end
  148. # Extract the action name from the passed data and process it via the channel. The process will ensure
  149. # that the action requested is a public method on the channel declared by the user (so not one of the callbacks
  150. # like #subscribed).
  151. 1 def perform_action(data)
  152. action = extract_action(data)
  153. if processable_action?(action)
  154. payload = { channel_class: self.class.name, action: action, data: data }
  155. ActiveSupport::Notifications.instrument("perform_action.action_cable", payload) do
  156. dispatch_action(action, data)
  157. end
  158. else
  159. logger.error "Unable to process #{action_signature(action, data)}"
  160. end
  161. end
  162. # This method is called after subscription has been added to the connection
  163. # and confirms or rejects the subscription.
  164. 1 def subscribe_to_channel
  165. run_callbacks :subscribe do
  166. subscribed
  167. end
  168. reject_subscription if subscription_rejected?
  169. ensure_confirmation_sent
  170. end
  171. # Called by the cable connection when it's cut, so the channel has a chance to cleanup with callbacks.
  172. # This method is not intended to be called directly by the user. Instead, overwrite the #unsubscribed callback.
  173. 1 def unsubscribe_from_channel # :nodoc:
  174. run_callbacks :unsubscribe do
  175. unsubscribed
  176. end
  177. end
  178. 1 private
  179. # Called once a consumer has become a subscriber of the channel. Usually the place to setup any streams
  180. # you want this channel to be sending to the subscriber.
  181. 1 def subscribed # :doc:
  182. # Override in subclasses
  183. end
  184. # Called once a consumer has cut its cable connection. Can be used for cleaning up connections or marking
  185. # users as offline or the like.
  186. 1 def unsubscribed # :doc:
  187. # Override in subclasses
  188. end
  189. # Transmit a hash of data to the subscriber. The hash will automatically be wrapped in a JSON envelope with
  190. # the proper channel identifier marked as the recipient.
  191. 1 def transmit(data, via: nil) # :doc:
  192. status = "#{self.class.name} transmitting #{data.inspect.truncate(300)}"
  193. status += " (via #{via})" if via
  194. logger.debug(status)
  195. payload = { channel_class: self.class.name, data: data, via: via }
  196. ActiveSupport::Notifications.instrument("transmit.action_cable", payload) do
  197. connection.transmit identifier: @identifier, message: data
  198. end
  199. end
  200. 1 def ensure_confirmation_sent # :doc:
  201. return if subscription_rejected?
  202. @defer_subscription_confirmation_counter.decrement
  203. transmit_subscription_confirmation unless defer_subscription_confirmation?
  204. end
  205. 1 def defer_subscription_confirmation! # :doc:
  206. @defer_subscription_confirmation_counter.increment
  207. end
  208. 1 def defer_subscription_confirmation? # :doc:
  209. @defer_subscription_confirmation_counter.value > 0
  210. end
  211. 1 def subscription_confirmation_sent? # :doc:
  212. @subscription_confirmation_sent
  213. end
  214. 1 def reject # :doc:
  215. @reject_subscription = true
  216. end
  217. 1 def subscription_rejected? # :doc:
  218. @reject_subscription
  219. end
  220. 1 def delegate_connection_identifiers
  221. connection.identifiers.each do |identifier|
  222. define_singleton_method(identifier) do
  223. connection.send(identifier)
  224. end
  225. end
  226. end
  227. 1 def extract_action(data)
  228. (data["action"].presence || :receive).to_sym
  229. end
  230. 1 def processable_action?(action)
  231. self.class.action_methods.include?(action.to_s) unless subscription_rejected?
  232. end
  233. 1 def dispatch_action(action, data)
  234. logger.info action_signature(action, data)
  235. if method(action).arity == 1
  236. public_send action, data
  237. else
  238. public_send action
  239. end
  240. end
  241. 1 def action_signature(action, data)
  242. "#{self.class.name}##{action}".dup.tap do |signature|
  243. if (arguments = data.except("action")).any?
  244. signature << "(#{arguments.inspect})"
  245. end
  246. end
  247. end
  248. 1 def transmit_subscription_confirmation
  249. unless subscription_confirmation_sent?
  250. logger.info "#{self.class.name} is transmitting the subscription confirmation"
  251. ActiveSupport::Notifications.instrument("transmit_subscription_confirmation.action_cable", channel_class: self.class.name) do
  252. connection.transmit identifier: @identifier, type: ActionCable::INTERNAL[:message_types][:confirmation]
  253. @subscription_confirmation_sent = true
  254. end
  255. end
  256. end
  257. 1 def reject_subscription
  258. connection.subscriptions.remove_subscription self
  259. transmit_subscription_rejection
  260. end
  261. 1 def transmit_subscription_rejection
  262. logger.info "#{self.class.name} is transmitting the subscription rejection"
  263. ActiveSupport::Notifications.instrument("transmit_subscription_rejection.action_cable", channel_class: self.class.name) do
  264. connection.transmit identifier: @identifier, type: ActionCable::INTERNAL[:message_types][:rejection]
  265. end
  266. end
  267. end
  268. end
  269. end

target/rubygems/gems/actioncable-5.2.3/lib/action_cable/channel/broadcasting.rb

64.29% lines covered

14 relevant lines. 9 lines covered and 5 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/object/to_param"
  3. 1 module ActionCable
  4. 1 module Channel
  5. 1 module Broadcasting
  6. 1 extend ActiveSupport::Concern
  7. 1 delegate :broadcasting_for, to: :class
  8. 1 module ClassMethods
  9. # Broadcast a hash to a unique broadcasting for this <tt>model</tt> in this channel.
  10. 1 def broadcast_to(model, message)
  11. ActionCable.server.broadcast(broadcasting_for([ channel_name, model ]), message)
  12. end
  13. 1 def broadcasting_for(model) #:nodoc:
  14. case
  15. when model.is_a?(Array)
  16. model.map { |m| broadcasting_for(m) }.join(":")
  17. when model.respond_to?(:to_gid_param)
  18. model.to_gid_param
  19. else
  20. model.to_param
  21. end
  22. end
  23. end
  24. end
  25. end
  26. end

target/rubygems/gems/actioncable-5.2.3/lib/action_cable/channel/callbacks.rb

90.0% lines covered

20 relevant lines. 18 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/callbacks"
  3. 1 module ActionCable
  4. 1 module Channel
  5. 1 module Callbacks
  6. 1 extend ActiveSupport::Concern
  7. 1 include ActiveSupport::Callbacks
  8. 1 included do
  9. 1 define_callbacks :subscribe
  10. 1 define_callbacks :unsubscribe
  11. end
  12. 1 module ClassMethods
  13. 1 def before_subscribe(*methods, &block)
  14. set_callback(:subscribe, :before, *methods, &block)
  15. end
  16. 1 def after_subscribe(*methods, &block)
  17. 1 set_callback(:subscribe, :after, *methods, &block)
  18. end
  19. 1 alias_method :on_subscribe, :after_subscribe
  20. 1 def before_unsubscribe(*methods, &block)
  21. set_callback(:unsubscribe, :before, *methods, &block)
  22. end
  23. 1 def after_unsubscribe(*methods, &block)
  24. 2 set_callback(:unsubscribe, :after, *methods, &block)
  25. end
  26. 1 alias_method :on_unsubscribe, :after_unsubscribe
  27. end
  28. end
  29. end
  30. end

target/rubygems/gems/actioncable-5.2.3/lib/action_cable/channel/naming.rb

87.5% lines covered

8 relevant lines. 7 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActionCable
  3. 1 module Channel
  4. 1 module Naming
  5. 1 extend ActiveSupport::Concern
  6. 1 module ClassMethods
  7. # Returns the name of the channel, underscored, without the <tt>Channel</tt> ending.
  8. # If the channel is in a namespace, then the namespaces are represented by single
  9. # colon separators in the channel name.
  10. #
  11. # ChatChannel.channel_name # => 'chat'
  12. # Chats::AppearancesChannel.channel_name # => 'chats:appearances'
  13. # FooChats::BarAppearancesChannel.channel_name # => 'foo_chats:bar_appearances'
  14. 1 def channel_name
  15. @channel_name ||= name.sub(/Channel$/, "").gsub("::", ":").underscore
  16. end
  17. end
  18. # Delegates to the class' <tt>channel_name</tt>
  19. 1 delegate :channel_name, to: :class
  20. end
  21. end
  22. end

target/rubygems/gems/actioncable-5.2.3/lib/action_cable/channel/periodic_timers.rb

46.88% lines covered

32 relevant lines. 15 lines covered and 17 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActionCable
  3. 1 module Channel
  4. 1 module PeriodicTimers
  5. 1 extend ActiveSupport::Concern
  6. 1 included do
  7. 1 class_attribute :periodic_timers, instance_reader: false, default: []
  8. 1 after_subscribe :start_periodic_timers
  9. 1 after_unsubscribe :stop_periodic_timers
  10. end
  11. 1 module ClassMethods
  12. # Periodically performs a task on the channel, like updating an online
  13. # user counter, polling a backend for new status messages, sending
  14. # regular "heartbeat" messages, or doing some internal work and giving
  15. # progress updates.
  16. #
  17. # Pass a method name or lambda argument or provide a block to call.
  18. # Specify the calling period in seconds using the <tt>every:</tt>
  19. # keyword argument.
  20. #
  21. # periodically :transmit_progress, every: 5.seconds
  22. #
  23. # periodically every: 3.minutes do
  24. # transmit action: :update_count, count: current_count
  25. # end
  26. #
  27. 1 def periodically(callback_or_method_name = nil, every:, &block)
  28. callback =
  29. if block_given?
  30. raise ArgumentError, "Pass a block or provide a callback arg, not both" if callback_or_method_name
  31. block
  32. else
  33. case callback_or_method_name
  34. when Proc
  35. callback_or_method_name
  36. when Symbol
  37. -> { __send__ callback_or_method_name }
  38. else
  39. raise ArgumentError, "Expected a Symbol method name or a Proc, got #{callback_or_method_name.inspect}"
  40. end
  41. end
  42. unless every.kind_of?(Numeric) && every > 0
  43. raise ArgumentError, "Expected every: to be a positive number of seconds, got #{every.inspect}"
  44. end
  45. self.periodic_timers += [[ callback, every: every ]]
  46. end
  47. end
  48. 1 private
  49. 1 def active_periodic_timers
  50. @active_periodic_timers ||= []
  51. end
  52. 1 def start_periodic_timers
  53. self.class.periodic_timers.each do |callback, options|
  54. active_periodic_timers << start_periodic_timer(callback, every: options.fetch(:every))
  55. end
  56. end
  57. 1 def start_periodic_timer(callback, every:)
  58. connection.server.event_loop.timer every do
  59. connection.worker_pool.async_exec self, connection: connection, &callback
  60. end
  61. end
  62. 1 def stop_periodic_timers
  63. active_periodic_timers.each { |timer| timer.shutdown }
  64. active_periodic_timers.clear
  65. end
  66. end
  67. end
  68. end

target/rubygems/gems/actioncable-5.2.3/lib/action_cable/channel/streams.rb

39.13% lines covered

46 relevant lines. 18 lines covered and 28 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActionCable
  3. 1 module Channel
  4. # Streams allow channels to route broadcastings to the subscriber. A broadcasting is, as discussed elsewhere, a pubsub queue where any data
  5. # placed into it is automatically sent to the clients that are connected at that time. It's purely an online queue, though. If you're not
  6. # streaming a broadcasting at the very moment it sends out an update, you will not get that update, even if you connect after it has been sent.
  7. #
  8. # Most commonly, the streamed broadcast is sent straight to the subscriber on the client-side. The channel just acts as a connector between
  9. # the two parties (the broadcaster and the channel subscriber). Here's an example of a channel that allows subscribers to get all new
  10. # comments on a given page:
  11. #
  12. # class CommentsChannel < ApplicationCable::Channel
  13. # def follow(data)
  14. # stream_from "comments_for_#{data['recording_id']}"
  15. # end
  16. #
  17. # def unfollow
  18. # stop_all_streams
  19. # end
  20. # end
  21. #
  22. # Based on the above example, the subscribers of this channel will get whatever data is put into the,
  23. # let's say, <tt>comments_for_45</tt> broadcasting as soon as it's put there.
  24. #
  25. # An example broadcasting for this channel looks like so:
  26. #
  27. # ActionCable.server.broadcast "comments_for_45", author: 'DHH', content: 'Rails is just swell'
  28. #
  29. # If you have a stream that is related to a model, then the broadcasting used can be generated from the model and channel.
  30. # The following example would subscribe to a broadcasting like <tt>comments:Z2lkOi8vVGVzdEFwcC9Qb3N0LzE</tt>.
  31. #
  32. # class CommentsChannel < ApplicationCable::Channel
  33. # def subscribed
  34. # post = Post.find(params[:id])
  35. # stream_for post
  36. # end
  37. # end
  38. #
  39. # You can then broadcast to this channel using:
  40. #
  41. # CommentsChannel.broadcast_to(@post, @comment)
  42. #
  43. # If you don't just want to parlay the broadcast unfiltered to the subscriber, you can also supply a callback that lets you alter what is sent out.
  44. # The below example shows how you can use this to provide performance introspection in the process:
  45. #
  46. # class ChatChannel < ApplicationCable::Channel
  47. # def subscribed
  48. # @room = Chat::Room[params[:room_number]]
  49. #
  50. # stream_for @room, coder: ActiveSupport::JSON do |message|
  51. # if message['originated_at'].present?
  52. # elapsed_time = (Time.now.to_f - message['originated_at']).round(2)
  53. #
  54. # ActiveSupport::Notifications.instrument :performance, measurement: 'Chat.message_delay', value: elapsed_time, action: :timing
  55. # logger.info "Message took #{elapsed_time}s to arrive"
  56. # end
  57. #
  58. # transmit message
  59. # end
  60. # end
  61. # end
  62. #
  63. # You can stop streaming from all broadcasts by calling #stop_all_streams.
  64. 1 module Streams
  65. 1 extend ActiveSupport::Concern
  66. 1 included do
  67. 1 on_unsubscribe :stop_all_streams
  68. end
  69. # Start streaming from the named <tt>broadcasting</tt> pubsub queue. Optionally, you can pass a <tt>callback</tt> that'll be used
  70. # instead of the default of just transmitting the updates straight to the subscriber.
  71. # Pass <tt>coder: ActiveSupport::JSON</tt> to decode messages as JSON before passing to the callback.
  72. # Defaults to <tt>coder: nil</tt> which does no decoding, passes raw messages.
  73. 1 def stream_from(broadcasting, callback = nil, coder: nil, &block)
  74. broadcasting = String(broadcasting)
  75. # Don't send the confirmation until pubsub#subscribe is successful
  76. defer_subscription_confirmation!
  77. # Build a stream handler by wrapping the user-provided callback with
  78. # a decoder or defaulting to a JSON-decoding retransmitter.
  79. handler = worker_pool_stream_handler(broadcasting, callback || block, coder: coder)
  80. streams << [ broadcasting, handler ]
  81. connection.server.event_loop.post do
  82. pubsub.subscribe(broadcasting, handler, lambda do
  83. ensure_confirmation_sent
  84. logger.info "#{self.class.name} is streaming from #{broadcasting}"
  85. end)
  86. end
  87. end
  88. # Start streaming the pubsub queue for the <tt>model</tt> in this channel. Optionally, you can pass a
  89. # <tt>callback</tt> that'll be used instead of the default of just transmitting the updates straight
  90. # to the subscriber.
  91. #
  92. # Pass <tt>coder: ActiveSupport::JSON</tt> to decode messages as JSON before passing to the callback.
  93. # Defaults to <tt>coder: nil</tt> which does no decoding, passes raw messages.
  94. 1 def stream_for(model, callback = nil, coder: nil, &block)
  95. stream_from(broadcasting_for([ channel_name, model ]), callback || block, coder: coder)
  96. end
  97. # Unsubscribes all streams associated with this channel from the pubsub queue.
  98. 1 def stop_all_streams
  99. streams.each do |broadcasting, callback|
  100. pubsub.unsubscribe broadcasting, callback
  101. logger.info "#{self.class.name} stopped streaming from #{broadcasting}"
  102. end.clear
  103. end
  104. 1 private
  105. 1 delegate :pubsub, to: :connection
  106. 1 def streams
  107. @_streams ||= []
  108. end
  109. # Always wrap the outermost handler to invoke the user handler on the
  110. # worker pool rather than blocking the event loop.
  111. 1 def worker_pool_stream_handler(broadcasting, user_handler, coder: nil)
  112. handler = stream_handler(broadcasting, user_handler, coder: coder)
  113. -> message do
  114. connection.worker_pool.async_invoke handler, :call, message, connection: connection
  115. end
  116. end
  117. # May be overridden to add instrumentation, logging, specialized error
  118. # handling, or other forms of handler decoration.
  119. #
  120. # TODO: Tests demonstrating this.
  121. 1 def stream_handler(broadcasting, user_handler, coder: nil)
  122. if user_handler
  123. stream_decoder user_handler, coder: coder
  124. else
  125. default_stream_handler broadcasting, coder: coder
  126. end
  127. end
  128. # May be overridden to change the default stream handling behavior
  129. # which decodes JSON and transmits to the client.
  130. #
  131. # TODO: Tests demonstrating this.
  132. #
  133. # TODO: Room for optimization. Update transmit API to be coder-aware
  134. # so we can no-op when pubsub and connection are both JSON-encoded.
  135. # Then we can skip decode+encode if we're just proxying messages.
  136. 1 def default_stream_handler(broadcasting, coder:)
  137. coder ||= ActiveSupport::JSON
  138. stream_transmitter stream_decoder(coder: coder), broadcasting: broadcasting
  139. end
  140. 1 def stream_decoder(handler = identity_handler, coder:)
  141. if coder
  142. -> message { handler.(coder.decode(message)) }
  143. else
  144. handler
  145. end
  146. end
  147. 1 def stream_transmitter(handler = identity_handler, broadcasting:)
  148. via = "streamed from #{broadcasting}"
  149. -> (message) do
  150. transmit handler.(message), via: via
  151. end
  152. end
  153. 1 def identity_handler
  154. -> message { message }
  155. end
  156. end
  157. end
  158. end

target/rubygems/gems/actioncable-5.2.3/lib/action_cable/server.rb

100.0% lines covered

10 relevant lines. 10 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActionCable
  3. 1 module Server
  4. 1 extend ActiveSupport::Autoload
  5. 1 eager_autoload do
  6. 1 autoload :Base
  7. 1 autoload :Broadcasting
  8. 1 autoload :Connections
  9. 1 autoload :Configuration
  10. 1 autoload :Worker
  11. 1 autoload :ActiveRecordConnectionManagement, "action_cable/server/worker/active_record_connection_management"
  12. end
  13. end
  14. end

target/rubygems/gems/actioncable-5.2.3/lib/action_cable/server/base.rb

61.11% lines covered

36 relevant lines. 22 lines covered and 14 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "monitor"
  3. 1 module ActionCable
  4. 1 module Server
  5. # A singleton ActionCable::Server instance is available via ActionCable.server. It's used by the Rack process that starts the Action Cable server, but
  6. # is also used by the user to reach the RemoteConnections object, which is used for finding and disconnecting connections across all servers.
  7. #
  8. # Also, this is the server instance used for broadcasting. See Broadcasting for more information.
  9. 1 class Base
  10. 1 include ActionCable::Server::Broadcasting
  11. 1 include ActionCable::Server::Connections
  12. 1 cattr_accessor :config, instance_accessor: true, default: ActionCable::Server::Configuration.new
  13. 1 def self.logger; config.logger; end
  14. 1 delegate :logger, to: :config
  15. 1 attr_reader :mutex
  16. 1 def initialize
  17. 1 @mutex = Monitor.new
  18. 1 @remote_connections = @event_loop = @worker_pool = @pubsub = nil
  19. end
  20. # Called by Rack to setup the server.
  21. 1 def call(env)
  22. setup_heartbeat_timer
  23. config.connection_class.call.new(self, env).process
  24. end
  25. # Disconnect all the connections identified by +identifiers+ on this server or any others via RemoteConnections.
  26. 1 def disconnect(identifiers)
  27. remote_connections.where(identifiers).disconnect
  28. end
  29. 1 def restart
  30. connections.each(&:close)
  31. @mutex.synchronize do
  32. # Shutdown the worker pool
  33. @worker_pool.halt if @worker_pool
  34. @worker_pool = nil
  35. # Shutdown the pub/sub adapter
  36. @pubsub.shutdown if @pubsub
  37. @pubsub = nil
  38. end
  39. end
  40. # Gateway to RemoteConnections. See that class for details.
  41. 1 def remote_connections
  42. @remote_connections || @mutex.synchronize { @remote_connections ||= RemoteConnections.new(self) }
  43. end
  44. 1 def event_loop
  45. @event_loop || @mutex.synchronize { @event_loop ||= ActionCable::Connection::StreamEventLoop.new }
  46. end
  47. # The worker pool is where we run connection callbacks and channel actions. We do as little as possible on the server's main thread.
  48. # The worker pool is an executor service that's backed by a pool of threads working from a task queue. The thread pool size maxes out
  49. # at 4 worker threads by default. Tune the size yourself with <tt>config.action_cable.worker_pool_size</tt>.
  50. #
  51. # Using Active Record, Redis, etc within your channel actions means you'll get a separate connection from each thread in the worker pool.
  52. # Plan your deployment accordingly: 5 servers each running 5 Puma workers each running an 8-thread worker pool means at least 200 database
  53. # connections.
  54. #
  55. # Also, ensure that your database connection pool size is as least as large as your worker pool size. Otherwise, workers may oversubscribe
  56. # the database connection pool and block while they wait for other workers to release their connections. Use a smaller worker pool or a larger
  57. # database connection pool instead.
  58. 1 def worker_pool
  59. @worker_pool || @mutex.synchronize { @worker_pool ||= ActionCable::Server::Worker.new(max_size: config.worker_pool_size) }
  60. end
  61. # Adapter used for all streams/broadcasting.
  62. 1 def pubsub
  63. @pubsub || @mutex.synchronize { @pubsub ||= config.pubsub_adapter.new(self) }
  64. end
  65. # All of the identifiers applied to the connection class associated with this server.
  66. 1 def connection_identifiers
  67. config.connection_class.call.identifiers
  68. end
  69. end
  70. 1 ActiveSupport.run_load_hooks(:action_cable, Base.config)
  71. end
  72. end

target/rubygems/gems/actioncable-5.2.3/lib/action_cable/server/broadcasting.rb

55.56% lines covered

18 relevant lines. 10 lines covered and 8 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActionCable
  3. 1 module Server
  4. # Broadcasting is how other parts of your application can send messages to a channel's subscribers. As explained in Channel, most of the time, these
  5. # broadcastings are streamed directly to the clients subscribed to the named broadcasting. Let's explain with a full-stack example:
  6. #
  7. # class WebNotificationsChannel < ApplicationCable::Channel
  8. # def subscribed
  9. # stream_from "web_notifications_#{current_user.id}"
  10. # end
  11. # end
  12. #
  13. # # Somewhere in your app this is called, perhaps from a NewCommentJob:
  14. # ActionCable.server.broadcast \
  15. # "web_notifications_1", { title: "New things!", body: "All that's fit for print" }
  16. #
  17. # # Client-side CoffeeScript, which assumes you've already requested the right to send web notifications:
  18. # App.cable.subscriptions.create "WebNotificationsChannel",
  19. # received: (data) ->
  20. # new Notification data['title'], body: data['body']
  21. 1 module Broadcasting
  22. # Broadcast a hash directly to a named <tt>broadcasting</tt>. This will later be JSON encoded.
  23. 1 def broadcast(broadcasting, message, coder: ActiveSupport::JSON)
  24. broadcaster_for(broadcasting, coder: coder).broadcast(message)
  25. end
  26. # Returns a broadcaster for a named <tt>broadcasting</tt> that can be reused. Useful when you have an object that
  27. # may need multiple spots to transmit to a specific broadcasting over and over.
  28. 1 def broadcaster_for(broadcasting, coder: ActiveSupport::JSON)
  29. Broadcaster.new(self, String(broadcasting), coder: coder)
  30. end
  31. 1 private
  32. 1 class Broadcaster
  33. 1 attr_reader :server, :broadcasting, :coder
  34. 1 def initialize(server, broadcasting, coder:)
  35. @server, @broadcasting, @coder = server, broadcasting, coder
  36. end
  37. 1 def broadcast(message)
  38. server.logger.debug "[ActionCable] Broadcasting to #{broadcasting}: #{message.inspect}"
  39. payload = { broadcasting: broadcasting, message: message, coder: coder }
  40. ActiveSupport::Notifications.instrument("broadcast.action_cable", payload) do
  41. encoded = coder ? coder.encode(message) : message
  42. server.pubsub.broadcast broadcasting, encoded
  43. end
  44. end
  45. end
  46. end
  47. end
  48. end

target/rubygems/gems/actioncable-5.2.3/lib/action_cable/server/configuration.rb

58.33% lines covered

24 relevant lines. 14 lines covered and 10 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActionCable
  3. 1 module Server
  4. # An instance of this configuration object is available via ActionCable.server.config, which allows you to tweak Action Cable configuration
  5. # in a Rails config initializer.
  6. 1 class Configuration
  7. 1 attr_accessor :logger, :log_tags
  8. 1 attr_accessor :connection_class, :worker_pool_size
  9. 1 attr_accessor :disable_request_forgery_protection, :allowed_request_origins, :allow_same_origin_as_host
  10. 1 attr_accessor :cable, :url, :mount_path
  11. 1 def initialize
  12. 1 @log_tags = []
  13. 1 @connection_class = -> { ActionCable::Connection::Base }
  14. 1 @worker_pool_size = 4
  15. 1 @disable_request_forgery_protection = false
  16. 1 @allow_same_origin_as_host = true
  17. end
  18. # Returns constant of subscription adapter specified in config/cable.yml.
  19. # If the adapter cannot be found, this will default to the Redis adapter.
  20. # Also makes sure proper dependencies are required.
  21. 1 def pubsub_adapter
  22. adapter = (cable.fetch("adapter") { "redis" })
  23. # Require the adapter itself and give useful feedback about
  24. # 1. Missing adapter gems and
  25. # 2. Adapter gems' missing dependencies.
  26. path_to_adapter = "action_cable/subscription_adapter/#{adapter}"
  27. begin
  28. require path_to_adapter
  29. rescue LoadError => e
  30. # We couldn't require the adapter itself. Raise an exception that
  31. # points out config typos and missing gems.
  32. if e.path == path_to_adapter
  33. # We can assume that a non-builtin adapter was specified, so it's
  34. # either misspelled or missing from Gemfile.
  35. raise e.class, "Could not load the '#{adapter}' Action Cable pubsub adapter. Ensure that the adapter is spelled correctly in config/cable.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace
  36. # Bubbled up from the adapter require. Prefix the exception message
  37. # with some guidance about how to address it and reraise.
  38. else
  39. raise e.class, "Error loading the '#{adapter}' Action Cable pubsub adapter. Missing a gem it depends on? #{e.message}", e.backtrace
  40. end
  41. end
  42. adapter = adapter.camelize
  43. adapter = "PostgreSQL" if adapter == "Postgresql"
  44. "ActionCable::SubscriptionAdapter::#{adapter}".constantize
  45. end
  46. end
  47. end
  48. end

target/rubygems/gems/actioncable-5.2.3/lib/action_cable/server/connections.rb

60.0% lines covered

15 relevant lines. 9 lines covered and 6 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActionCable
  3. 1 module Server
  4. # Collection class for all the connections that have been established on this specific server. Remember, usually you'll run many Action Cable servers, so
  5. # you can't use this collection as a full list of all of the connections established against your application. Instead, use RemoteConnections for that.
  6. 1 module Connections # :nodoc:
  7. 1 BEAT_INTERVAL = 3
  8. 1 def connections
  9. @connections ||= []
  10. end
  11. 1 def add_connection(connection)
  12. connections << connection
  13. end
  14. 1 def remove_connection(connection)
  15. connections.delete connection
  16. end
  17. # WebSocket connection implementations differ on when they'll mark a connection as stale. We basically never want a connection to go stale, as you
  18. # then can't rely on being able to communicate with the connection. To solve this, a 3 second heartbeat runs on all connections. If the beat fails, we automatically
  19. # disconnect.
  20. 1 def setup_heartbeat_timer
  21. @heartbeat_timer ||= event_loop.timer(BEAT_INTERVAL) do
  22. event_loop.post { connections.map(&:beat) }
  23. end
  24. end
  25. 1 def open_connections_statistics
  26. connections.map(&:statistics)
  27. end
  28. end
  29. end
  30. end

target/rubygems/gems/actioncable-5.2.3/lib/action_cable/server/worker.rb

54.05% lines covered

37 relevant lines. 20 lines covered and 17 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/callbacks"
  3. 1 require "active_support/core_ext/module/attribute_accessors_per_thread"
  4. 1 require "concurrent"
  5. 1 module ActionCable
  6. 1 module Server
  7. # Worker used by Server.send_async to do connection work in threads.
  8. 1 class Worker # :nodoc:
  9. 1 include ActiveSupport::Callbacks
  10. 1 thread_mattr_accessor :connection
  11. 1 define_callbacks :work
  12. 1 include ActiveRecordConnectionManagement
  13. 1 attr_reader :executor
  14. 1 def initialize(max_size: 5)
  15. @executor = Concurrent::ThreadPoolExecutor.new(
  16. min_threads: 1,
  17. max_threads: max_size,
  18. max_queue: 0,
  19. )
  20. end
  21. # Stop processing work: any work that has not already started
  22. # running will be discarded from the queue
  23. 1 def halt
  24. @executor.shutdown
  25. end
  26. 1 def stopping?
  27. @executor.shuttingdown?
  28. end
  29. 1 def work(connection)
  30. self.connection = connection
  31. run_callbacks :work do
  32. yield
  33. end
  34. ensure
  35. self.connection = nil
  36. end
  37. 1 def async_exec(receiver, *args, connection:, &block)
  38. async_invoke receiver, :instance_exec, *args, connection: connection, &block
  39. end
  40. 1 def async_invoke(receiver, method, *args, connection: receiver, &block)
  41. @executor.post do
  42. invoke(receiver, method, *args, connection: connection, &block)
  43. end
  44. end
  45. 1 def invoke(receiver, method, *args, connection:, &block)
  46. work(connection) do
  47. begin
  48. receiver.send method, *args, &block
  49. rescue Exception => e
  50. logger.error "There was an exception - #{e.class}(#{e.message})"
  51. logger.error e.backtrace.join("\n")
  52. receiver.handle_exception if receiver.respond_to?(:handle_exception)
  53. end
  54. end
  55. end
  56. 1 private
  57. 1 def logger
  58. ActionCable.server.logger
  59. end
  60. end
  61. end
  62. end

target/rubygems/gems/actioncable-5.2.3/lib/action_cable/server/worker/active_record_connection_management.rb

90.0% lines covered

10 relevant lines. 9 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActionCable
  3. 1 module Server
  4. 1 class Worker
  5. 1 module ActiveRecordConnectionManagement
  6. 1 extend ActiveSupport::Concern
  7. 1 included do
  8. 1 if defined?(ActiveRecord::Base)
  9. 1 set_callback :work, :around, :with_database_connections
  10. end
  11. end
  12. 1 def with_database_connections
  13. connection.logger.tag(ActiveRecord::Base.logger) { yield }
  14. end
  15. end
  16. end
  17. end
  18. end

target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/base.rb

42.19% lines covered

192 relevant lines. 81 lines covered and 111 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "mail"
  3. 1 require "action_mailer/collector"
  4. 1 require "active_support/core_ext/string/inflections"
  5. 1 require "active_support/core_ext/hash/except"
  6. 1 require "active_support/core_ext/module/anonymous"
  7. 1 require "action_mailer/log_subscriber"
  8. 1 require "action_mailer/rescuable"
  9. 1 module ActionMailer
  10. # Action Mailer allows you to send email from your application using a mailer model and views.
  11. #
  12. # = Mailer Models
  13. #
  14. # To use Action Mailer, you need to create a mailer model.
  15. #
  16. # $ rails generate mailer Notifier
  17. #
  18. # The generated model inherits from <tt>ApplicationMailer</tt> which in turn
  19. # inherits from <tt>ActionMailer::Base</tt>. A mailer model defines methods
  20. # used to generate an email message. In these methods, you can setup variables to be used in
  21. # the mailer views, options on the mail itself such as the <tt>:from</tt> address, and attachments.
  22. #
  23. # class ApplicationMailer < ActionMailer::Base
  24. # default from: 'from@example.com'
  25. # layout 'mailer'
  26. # end
  27. #
  28. # class NotifierMailer < ApplicationMailer
  29. # default from: 'no-reply@example.com',
  30. # return_path: 'system@example.com'
  31. #
  32. # def welcome(recipient)
  33. # @account = recipient
  34. # mail(to: recipient.email_address_with_name,
  35. # bcc: ["bcc@example.com", "Order Watcher <watcher@example.com>"])
  36. # end
  37. # end
  38. #
  39. # Within the mailer method, you have access to the following methods:
  40. #
  41. # * <tt>attachments[]=</tt> - Allows you to add attachments to your email in an intuitive
  42. # manner; <tt>attachments['filename.png'] = File.read('path/to/filename.png')</tt>
  43. #
  44. # * <tt>attachments.inline[]=</tt> - Allows you to add an inline attachment to your email
  45. # in the same manner as <tt>attachments[]=</tt>
  46. #
  47. # * <tt>headers[]=</tt> - Allows you to specify any header field in your email such
  48. # as <tt>headers['X-No-Spam'] = 'True'</tt>. Note that declaring a header multiple times
  49. # will add many fields of the same name. Read #headers doc for more information.
  50. #
  51. # * <tt>headers(hash)</tt> - Allows you to specify multiple headers in your email such
  52. # as <tt>headers({'X-No-Spam' => 'True', 'In-Reply-To' => '1234@message.id'})</tt>
  53. #
  54. # * <tt>mail</tt> - Allows you to specify email to be sent.
  55. #
  56. # The hash passed to the mail method allows you to specify any header that a <tt>Mail::Message</tt>
  57. # will accept (any valid email header including optional fields).
  58. #
  59. # The +mail+ method, if not passed a block, will inspect your views and send all the views with
  60. # the same name as the method, so the above action would send the +welcome.text.erb+ view
  61. # file as well as the +welcome.html.erb+ view file in a +multipart/alternative+ email.
  62. #
  63. # If you want to explicitly render only certain templates, pass a block:
  64. #
  65. # mail(to: user.email) do |format|
  66. # format.text
  67. # format.html
  68. # end
  69. #
  70. # The block syntax is also useful in providing information specific to a part:
  71. #
  72. # mail(to: user.email) do |format|
  73. # format.text(content_transfer_encoding: "base64")
  74. # format.html
  75. # end
  76. #
  77. # Or even to render a special view:
  78. #
  79. # mail(to: user.email) do |format|
  80. # format.text
  81. # format.html { render "some_other_template" }
  82. # end
  83. #
  84. # = Mailer views
  85. #
  86. # Like Action Controller, each mailer class has a corresponding view directory in which each
  87. # method of the class looks for a template with its name.
  88. #
  89. # To define a template to be used with a mailer, create an <tt>.erb</tt> file with the same
  90. # name as the method in your mailer model. For example, in the mailer defined above, the template at
  91. # <tt>app/views/notifier_mailer/welcome.text.erb</tt> would be used to generate the email.
  92. #
  93. # Variables defined in the methods of your mailer model are accessible as instance variables in their
  94. # corresponding view.
  95. #
  96. # Emails by default are sent in plain text, so a sample view for our model example might look like this:
  97. #
  98. # Hi <%= @account.name %>,
  99. # Thanks for joining our service! Please check back often.
  100. #
  101. # You can even use Action View helpers in these views. For example:
  102. #
  103. # You got a new note!
  104. # <%= truncate(@note.body, length: 25) %>
  105. #
  106. # If you need to access the subject, from or the recipients in the view, you can do that through message object:
  107. #
  108. # You got a new note from <%= message.from %>!
  109. # <%= truncate(@note.body, length: 25) %>
  110. #
  111. #
  112. # = Generating URLs
  113. #
  114. # URLs can be generated in mailer views using <tt>url_for</tt> or named routes. Unlike controllers from
  115. # Action Pack, the mailer instance doesn't have any context about the incoming request, so you'll need
  116. # to provide all of the details needed to generate a URL.
  117. #
  118. # When using <tt>url_for</tt> you'll need to provide the <tt>:host</tt>, <tt>:controller</tt>, and <tt>:action</tt>:
  119. #
  120. # <%= url_for(host: "example.com", controller: "welcome", action: "greeting") %>
  121. #
  122. # When using named routes you only need to supply the <tt>:host</tt>:
  123. #
  124. # <%= users_url(host: "example.com") %>
  125. #
  126. # You should use the <tt>named_route_url</tt> style (which generates absolute URLs) and avoid using the
  127. # <tt>named_route_path</tt> style (which generates relative URLs), since clients reading the mail will
  128. # have no concept of a current URL from which to determine a relative path.
  129. #
  130. # It is also possible to set a default host that will be used in all mailers by setting the <tt>:host</tt>
  131. # option as a configuration option in <tt>config/application.rb</tt>:
  132. #
  133. # config.action_mailer.default_url_options = { host: "example.com" }
  134. #
  135. # You can also define a <tt>default_url_options</tt> method on individual mailers to override these
  136. # default settings per-mailer.
  137. #
  138. # By default when <tt>config.force_ssl</tt> is +true+, URLs generated for hosts will use the HTTPS protocol.
  139. #
  140. # = Sending mail
  141. #
  142. # Once a mailer action and template are defined, you can deliver your message or defer its creation and
  143. # delivery for later:
  144. #
  145. # NotifierMailer.welcome(User.first).deliver_now # sends the email
  146. # mail = NotifierMailer.welcome(User.first) # => an ActionMailer::MessageDelivery object
  147. # mail.deliver_now # generates and sends the email now
  148. #
  149. # The <tt>ActionMailer::MessageDelivery</tt> class is a wrapper around a delegate that will call
  150. # your method to generate the mail. If you want direct access to the delegator, or <tt>Mail::Message</tt>,
  151. # you can call the <tt>message</tt> method on the <tt>ActionMailer::MessageDelivery</tt> object.
  152. #
  153. # NotifierMailer.welcome(User.first).message # => a Mail::Message object
  154. #
  155. # Action Mailer is nicely integrated with Active Job so you can generate and send emails in the background
  156. # (example: outside of the request-response cycle, so the user doesn't have to wait on it):
  157. #
  158. # NotifierMailer.welcome(User.first).deliver_later # enqueue the email sending to Active Job
  159. #
  160. # Note that <tt>deliver_later</tt> will execute your method from the background job.
  161. #
  162. # You never instantiate your mailer class. Rather, you just call the method you defined on the class itself.
  163. # All instance methods are expected to return a message object to be sent.
  164. #
  165. # = Multipart Emails
  166. #
  167. # Multipart messages can also be used implicitly because Action Mailer will automatically detect and use
  168. # multipart templates, where each template is named after the name of the action, followed by the content
  169. # type. Each such detected template will be added to the message, as a separate part.
  170. #
  171. # For example, if the following templates exist:
  172. # * signup_notification.text.erb
  173. # * signup_notification.html.erb
  174. # * signup_notification.xml.builder
  175. # * signup_notification.yml.erb
  176. #
  177. # Each would be rendered and added as a separate part to the message, with the corresponding content
  178. # type. The content type for the entire message is automatically set to <tt>multipart/alternative</tt>,
  179. # which indicates that the email contains multiple different representations of the same email
  180. # body. The same instance variables defined in the action are passed to all email templates.
  181. #
  182. # Implicit template rendering is not performed if any attachments or parts have been added to the email.
  183. # This means that you'll have to manually add each part to the email and set the content type of the email
  184. # to <tt>multipart/alternative</tt>.
  185. #
  186. # = Attachments
  187. #
  188. # Sending attachment in emails is easy:
  189. #
  190. # class NotifierMailer < ApplicationMailer
  191. # def welcome(recipient)
  192. # attachments['free_book.pdf'] = File.read('path/to/file.pdf')
  193. # mail(to: recipient, subject: "New account information")
  194. # end
  195. # end
  196. #
  197. # Which will (if it had both a <tt>welcome.text.erb</tt> and <tt>welcome.html.erb</tt>
  198. # template in the view directory), send a complete <tt>multipart/mixed</tt> email with two parts,
  199. # the first part being a <tt>multipart/alternative</tt> with the text and HTML email parts inside,
  200. # and the second being a <tt>application/pdf</tt> with a Base64 encoded copy of the file.pdf book
  201. # with the filename +free_book.pdf+.
  202. #
  203. # If you need to send attachments with no content, you need to create an empty view for it,
  204. # or add an empty body parameter like this:
  205. #
  206. # class NotifierMailer < ApplicationMailer
  207. # def welcome(recipient)
  208. # attachments['free_book.pdf'] = File.read('path/to/file.pdf')
  209. # mail(to: recipient, subject: "New account information", body: "")
  210. # end
  211. # end
  212. #
  213. # You can also send attachments with html template, in this case you need to add body, attachments,
  214. # and custom content type like this:
  215. #
  216. # class NotifierMailer < ApplicationMailer
  217. # def welcome(recipient)
  218. # attachments["free_book.pdf"] = File.read("path/to/file.pdf")
  219. # mail(to: recipient,
  220. # subject: "New account information",
  221. # content_type: "text/html",
  222. # body: "<html><body>Hello there</body></html>")
  223. # end
  224. # end
  225. #
  226. # = Inline Attachments
  227. #
  228. # You can also specify that a file should be displayed inline with other HTML. This is useful
  229. # if you want to display a corporate logo or a photo.
  230. #
  231. # class NotifierMailer < ApplicationMailer
  232. # def welcome(recipient)
  233. # attachments.inline['photo.png'] = File.read('path/to/photo.png')
  234. # mail(to: recipient, subject: "Here is what we look like")
  235. # end
  236. # end
  237. #
  238. # And then to reference the image in the view, you create a <tt>welcome.html.erb</tt> file and
  239. # make a call to +image_tag+ passing in the attachment you want to display and then call
  240. # +url+ on the attachment to get the relative content id path for the image source:
  241. #
  242. # <h1>Please Don't Cringe</h1>
  243. #
  244. # <%= image_tag attachments['photo.png'].url -%>
  245. #
  246. # As we are using Action View's +image_tag+ method, you can pass in any other options you want:
  247. #
  248. # <h1>Please Don't Cringe</h1>
  249. #
  250. # <%= image_tag attachments['photo.png'].url, alt: 'Our Photo', class: 'photo' -%>
  251. #
  252. # = Observing and Intercepting Mails
  253. #
  254. # Action Mailer provides hooks into the Mail observer and interceptor methods. These allow you to
  255. # register classes that are called during the mail delivery life cycle.
  256. #
  257. # An observer class must implement the <tt>:delivered_email(message)</tt> method which will be
  258. # called once for every email sent after the email has been sent.
  259. #
  260. # An interceptor class must implement the <tt>:delivering_email(message)</tt> method which will be
  261. # called before the email is sent, allowing you to make modifications to the email before it hits
  262. # the delivery agents. Your class should make any needed modifications directly to the passed
  263. # in <tt>Mail::Message</tt> instance.
  264. #
  265. # = Default Hash
  266. #
  267. # Action Mailer provides some intelligent defaults for your emails, these are usually specified in a
  268. # default method inside the class definition:
  269. #
  270. # class NotifierMailer < ApplicationMailer
  271. # default sender: 'system@example.com'
  272. # end
  273. #
  274. # You can pass in any header value that a <tt>Mail::Message</tt> accepts. Out of the box,
  275. # <tt>ActionMailer::Base</tt> sets the following:
  276. #
  277. # * <tt>mime_version: "1.0"</tt>
  278. # * <tt>charset: "UTF-8"</tt>
  279. # * <tt>content_type: "text/plain"</tt>
  280. # * <tt>parts_order: [ "text/plain", "text/enriched", "text/html" ]</tt>
  281. #
  282. # <tt>parts_order</tt> and <tt>charset</tt> are not actually valid <tt>Mail::Message</tt> header fields,
  283. # but Action Mailer translates them appropriately and sets the correct values.
  284. #
  285. # As you can pass in any header, you need to either quote the header as a string, or pass it in as
  286. # an underscored symbol, so the following will work:
  287. #
  288. # class NotifierMailer < ApplicationMailer
  289. # default 'Content-Transfer-Encoding' => '7bit',
  290. # content_description: 'This is a description'
  291. # end
  292. #
  293. # Finally, Action Mailer also supports passing <tt>Proc</tt> and <tt>Lambda</tt> objects into the default hash,
  294. # so you can define methods that evaluate as the message is being generated:
  295. #
  296. # class NotifierMailer < ApplicationMailer
  297. # default 'X-Special-Header' => Proc.new { my_method }, to: -> { @inviter.email_address }
  298. #
  299. # private
  300. # def my_method
  301. # 'some complex call'
  302. # end
  303. # end
  304. #
  305. # Note that the proc/lambda is evaluated right at the start of the mail message generation, so if you
  306. # set something in the default hash using a proc, and then set the same thing inside of your
  307. # mailer method, it will get overwritten by the mailer method.
  308. #
  309. # It is also possible to set these default options that will be used in all mailers through
  310. # the <tt>default_options=</tt> configuration in <tt>config/application.rb</tt>:
  311. #
  312. # config.action_mailer.default_options = { from: "no-reply@example.org" }
  313. #
  314. # = Callbacks
  315. #
  316. # You can specify callbacks using <tt>before_action</tt> and <tt>after_action</tt> for configuring your messages.
  317. # This may be useful, for example, when you want to add default inline attachments for all
  318. # messages sent out by a certain mailer class:
  319. #
  320. # class NotifierMailer < ApplicationMailer
  321. # before_action :add_inline_attachment!
  322. #
  323. # def welcome
  324. # mail
  325. # end
  326. #
  327. # private
  328. # def add_inline_attachment!
  329. # attachments.inline["footer.jpg"] = File.read('/path/to/filename.jpg')
  330. # end
  331. # end
  332. #
  333. # Callbacks in Action Mailer are implemented using
  334. # <tt>AbstractController::Callbacks</tt>, so you can define and configure
  335. # callbacks in the same manner that you would use callbacks in classes that
  336. # inherit from <tt>ActionController::Base</tt>.
  337. #
  338. # Note that unless you have a specific reason to do so, you should prefer
  339. # using <tt>before_action</tt> rather than <tt>after_action</tt> in your
  340. # Action Mailer classes so that headers are parsed properly.
  341. #
  342. # = Previewing emails
  343. #
  344. # You can preview your email templates visually by adding a mailer preview file to the
  345. # <tt>ActionMailer::Base.preview_path</tt>. Since most emails do something interesting
  346. # with database data, you'll need to write some scenarios to load messages with fake data:
  347. #
  348. # class NotifierMailerPreview < ActionMailer::Preview
  349. # def welcome
  350. # NotifierMailer.welcome(User.first)
  351. # end
  352. # end
  353. #
  354. # Methods must return a <tt>Mail::Message</tt> object which can be generated by calling the mailer
  355. # method without the additional <tt>deliver_now</tt> / <tt>deliver_later</tt>. The location of the
  356. # mailer previews directory can be configured using the <tt>preview_path</tt> option which has a default
  357. # of <tt>test/mailers/previews</tt>:
  358. #
  359. # config.action_mailer.preview_path = "#{Rails.root}/lib/mailer_previews"
  360. #
  361. # An overview of all previews is accessible at <tt>http://localhost:3000/rails/mailers</tt>
  362. # on a running development server instance.
  363. #
  364. # Previews can also be intercepted in a similar manner as deliveries can be by registering
  365. # a preview interceptor that has a <tt>previewing_email</tt> method:
  366. #
  367. # class CssInlineStyler
  368. # def self.previewing_email(message)
  369. # # inline CSS styles
  370. # end
  371. # end
  372. #
  373. # config.action_mailer.preview_interceptors :css_inline_styler
  374. #
  375. # Note that interceptors need to be registered both with <tt>register_interceptor</tt>
  376. # and <tt>register_preview_interceptor</tt> if they should operate on both sending and
  377. # previewing emails.
  378. #
  379. # = Configuration options
  380. #
  381. # These options are specified on the class level, like
  382. # <tt>ActionMailer::Base.raise_delivery_errors = true</tt>
  383. #
  384. # * <tt>default_options</tt> - You can pass this in at a class level as well as within the class itself as
  385. # per the above section.
  386. #
  387. # * <tt>logger</tt> - the logger is used for generating information on the mailing run if available.
  388. # Can be set to +nil+ for no logging. Compatible with both Ruby's own +Logger+ and Log4r loggers.
  389. #
  390. # * <tt>smtp_settings</tt> - Allows detailed configuration for <tt>:smtp</tt> delivery method:
  391. # * <tt>:address</tt> - Allows you to use a remote mail server. Just change it from its default
  392. # "localhost" setting.
  393. # * <tt>:port</tt> - On the off chance that your mail server doesn't run on port 25, you can change it.
  394. # * <tt>:domain</tt> - If you need to specify a HELO domain, you can do it here.
  395. # * <tt>:user_name</tt> - If your mail server requires authentication, set the username in this setting.
  396. # * <tt>:password</tt> - If your mail server requires authentication, set the password in this setting.
  397. # * <tt>:authentication</tt> - If your mail server requires authentication, you need to specify the
  398. # authentication type here.
  399. # This is a symbol and one of <tt>:plain</tt> (will send the password Base64 encoded), <tt>:login</tt> (will
  400. # send the password Base64 encoded) or <tt>:cram_md5</tt> (combines a Challenge/Response mechanism to exchange
  401. # information and a cryptographic Message Digest 5 algorithm to hash important information)
  402. # * <tt>:enable_starttls_auto</tt> - Detects if STARTTLS is enabled in your SMTP server and starts
  403. # to use it. Defaults to <tt>true</tt>.
  404. # * <tt>:openssl_verify_mode</tt> - When using TLS, you can set how OpenSSL checks the certificate. This is
  405. # really useful if you need to validate a self-signed and/or a wildcard certificate. You can use the name
  406. # of an OpenSSL verify constant (<tt>'none'</tt> or <tt>'peer'</tt>) or directly the constant
  407. # (<tt>OpenSSL::SSL::VERIFY_NONE</tt> or <tt>OpenSSL::SSL::VERIFY_PEER</tt>).
  408. # <tt>:ssl/:tls</tt> Enables the SMTP connection to use SMTP/TLS (SMTPS: SMTP over direct TLS connection)
  409. #
  410. # * <tt>sendmail_settings</tt> - Allows you to override options for the <tt>:sendmail</tt> delivery method.
  411. # * <tt>:location</tt> - The location of the sendmail executable. Defaults to <tt>/usr/sbin/sendmail</tt>.
  412. # * <tt>:arguments</tt> - The command line arguments. Defaults to <tt>-i</tt> with <tt>-f sender@address</tt>
  413. # added automatically before the message is sent.
  414. #
  415. # * <tt>file_settings</tt> - Allows you to override options for the <tt>:file</tt> delivery method.
  416. # * <tt>:location</tt> - The directory into which emails will be written. Defaults to the application
  417. # <tt>tmp/mails</tt>.
  418. #
  419. # * <tt>raise_delivery_errors</tt> - Whether or not errors should be raised if the email fails to be delivered.
  420. #
  421. # * <tt>delivery_method</tt> - Defines a delivery method. Possible values are <tt>:smtp</tt> (default),
  422. # <tt>:sendmail</tt>, <tt>:test</tt>, and <tt>:file</tt>. Or you may provide a custom delivery method
  423. # object e.g. +MyOwnDeliveryMethodClass+. See the Mail gem documentation on the interface you need to
  424. # implement for a custom delivery agent.
  425. #
  426. # * <tt>perform_deliveries</tt> - Determines whether emails are actually sent from Action Mailer when you
  427. # call <tt>.deliver</tt> on an email message or on an Action Mailer method. This is on by default but can
  428. # be turned off to aid in functional testing.
  429. #
  430. # * <tt>deliveries</tt> - Keeps an array of all the emails sent out through the Action Mailer with
  431. # <tt>delivery_method :test</tt>. Most useful for unit and functional testing.
  432. #
  433. # * <tt>deliver_later_queue_name</tt> - The name of the queue used with <tt>deliver_later</tt>. Defaults to +mailers+.
  434. 1 class Base < AbstractController::Base
  435. 1 include DeliveryMethods
  436. 1 include Rescuable
  437. 1 include Parameterized
  438. 1 include Previews
  439. 1 abstract!
  440. 1 include AbstractController::Rendering
  441. 1 include AbstractController::Logger
  442. 1 include AbstractController::Helpers
  443. 1 include AbstractController::Translation
  444. 1 include AbstractController::AssetPaths
  445. 1 include AbstractController::Callbacks
  446. 1 include AbstractController::Caching
  447. 1 include ActionView::Layouts
  448. 1 PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + [:@_action_has_layout]
  449. 1 def _protected_ivars # :nodoc:
  450. PROTECTED_IVARS
  451. end
  452. 1 helper ActionMailer::MailHelper
  453. 1 class_attribute :delivery_job, default: ::ActionMailer::DeliveryJob
  454. 1 class_attribute :default_params, default: {
  455. mime_version: "1.0",
  456. charset: "UTF-8",
  457. content_type: "text/plain",
  458. parts_order: [ "text/plain", "text/enriched", "text/html" ]
  459. }.freeze
  460. 1 class << self
  461. # Register one or more Observers which will be notified when mail is delivered.
  462. 1 def register_observers(*observers)
  463. 1 observers.flatten.compact.each { |observer| register_observer(observer) }
  464. end
  465. # Register one or more Interceptors which will be called before mail is sent.
  466. 1 def register_interceptors(*interceptors)
  467. 1 interceptors.flatten.compact.each { |interceptor| register_interceptor(interceptor) }
  468. end
  469. # Register an Observer which will be notified when mail is delivered.
  470. # Either a class, string or symbol can be passed in as the Observer.
  471. # If a string or symbol is passed in it will be camelized and constantized.
  472. 1 def register_observer(observer)
  473. Mail.register_observer(observer_class_for(observer))
  474. end
  475. # Register an Interceptor which will be called before mail is sent.
  476. # Either a class, string or symbol can be passed in as the Interceptor.
  477. # If a string or symbol is passed in it will be camelized and constantized.
  478. 1 def register_interceptor(interceptor)
  479. Mail.register_interceptor(observer_class_for(interceptor))
  480. end
  481. 1 def observer_class_for(value) # :nodoc:
  482. case value
  483. when String, Symbol
  484. value.to_s.camelize.constantize
  485. else
  486. value
  487. end
  488. end
  489. 1 private :observer_class_for
  490. # Returns the name of the current mailer. This method is also being used as a path for a view lookup.
  491. # If this is an anonymous mailer, this method will return +anonymous+ instead.
  492. 1 def mailer_name
  493. @mailer_name ||= anonymous? ? "anonymous" : name.underscore
  494. end
  495. # Allows to set the name of current mailer.
  496. 1 attr_writer :mailer_name
  497. 1 alias :controller_path :mailer_name
  498. # Sets the defaults through app configuration:
  499. #
  500. # config.action_mailer.default(from: "no-reply@example.org")
  501. #
  502. # Aliased by ::default_options=
  503. 1 def default(value = nil)
  504. self.default_params = default_params.merge(value).freeze if value
  505. default_params
  506. end
  507. # Allows to set defaults through app configuration:
  508. #
  509. # config.action_mailer.default_options = { from: "no-reply@example.org" }
  510. 1 alias :default_options= :default
  511. # Receives a raw email, parses it into an email object, decodes it,
  512. # instantiates a new mailer, and passes the email object to the mailer
  513. # object's +receive+ method.
  514. #
  515. # If you want your mailer to be able to process incoming messages, you'll
  516. # need to implement a +receive+ method that accepts the raw email string
  517. # as a parameter:
  518. #
  519. # class MyMailer < ActionMailer::Base
  520. # def receive(mail)
  521. # # ...
  522. # end
  523. # end
  524. 1 def receive(raw_mail)
  525. ActiveSupport::Notifications.instrument("receive.action_mailer") do |payload|
  526. mail = Mail.new(raw_mail)
  527. set_payload_for_mail(payload, mail)
  528. new.receive(mail)
  529. end
  530. end
  531. # Wraps an email delivery inside of <tt>ActiveSupport::Notifications</tt> instrumentation.
  532. #
  533. # This method is actually called by the <tt>Mail::Message</tt> object itself
  534. # through a callback when you call <tt>:deliver</tt> on the <tt>Mail::Message</tt>,
  535. # calling +deliver_mail+ directly and passing a <tt>Mail::Message</tt> will do
  536. # nothing except tell the logger you sent the email.
  537. 1 def deliver_mail(mail) #:nodoc:
  538. ActiveSupport::Notifications.instrument("deliver.action_mailer") do |payload|
  539. set_payload_for_mail(payload, mail)
  540. yield # Let Mail do the delivery actions
  541. end
  542. end
  543. 1 private
  544. 1 def set_payload_for_mail(payload, mail)
  545. payload[:mailer] = name
  546. payload[:message_id] = mail.message_id
  547. payload[:subject] = mail.subject
  548. payload[:to] = mail.to
  549. payload[:from] = mail.from
  550. payload[:bcc] = mail.bcc if mail.bcc.present?
  551. payload[:cc] = mail.cc if mail.cc.present?
  552. payload[:date] = mail.date
  553. payload[:mail] = mail.encoded
  554. end
  555. 1 def method_missing(method_name, *args)
  556. if action_methods.include?(method_name.to_s)
  557. MessageDelivery.new(self, method_name, *args)
  558. else
  559. super
  560. end
  561. end
  562. 1 def respond_to_missing?(method, include_all = false)
  563. 1 action_methods.include?(method.to_s) || super
  564. end
  565. end
  566. 1 attr_internal :message
  567. 1 def initialize
  568. super()
  569. @_mail_was_called = false
  570. @_message = Mail.new
  571. end
  572. 1 def process(method_name, *args) #:nodoc:
  573. payload = {
  574. mailer: self.class.name,
  575. action: method_name,
  576. args: args
  577. }
  578. ActiveSupport::Notifications.instrument("process.action_mailer", payload) do
  579. super
  580. @_message = NullMail.new unless @_mail_was_called
  581. end
  582. end
  583. 1 class NullMail #:nodoc:
  584. 1 def body; "" end
  585. 1 def header; {} end
  586. 1 def respond_to?(string, include_all = false)
  587. true
  588. end
  589. 1 def method_missing(*args)
  590. nil
  591. end
  592. end
  593. # Returns the name of the mailer object.
  594. 1 def mailer_name
  595. self.class.mailer_name
  596. end
  597. # Allows you to pass random and unusual headers to the new <tt>Mail::Message</tt>
  598. # object which will add them to itself.
  599. #
  600. # headers['X-Special-Domain-Specific-Header'] = "SecretValue"
  601. #
  602. # You can also pass a hash into headers of header field names and values,
  603. # which will then be set on the <tt>Mail::Message</tt> object:
  604. #
  605. # headers 'X-Special-Domain-Specific-Header' => "SecretValue",
  606. # 'In-Reply-To' => incoming.message_id
  607. #
  608. # The resulting <tt>Mail::Message</tt> will have the following in its header:
  609. #
  610. # X-Special-Domain-Specific-Header: SecretValue
  611. #
  612. # Note about replacing already defined headers:
  613. #
  614. # * +subject+
  615. # * +sender+
  616. # * +from+
  617. # * +to+
  618. # * +cc+
  619. # * +bcc+
  620. # * +reply-to+
  621. # * +orig-date+
  622. # * +message-id+
  623. # * +references+
  624. #
  625. # Fields can only appear once in email headers while other fields such as
  626. # <tt>X-Anything</tt> can appear multiple times.
  627. #
  628. # If you want to replace any header which already exists, first set it to
  629. # +nil+ in order to reset the value otherwise another field will be added
  630. # for the same header.
  631. 1 def headers(args = nil)
  632. if args
  633. @_message.headers(args)
  634. else
  635. @_message
  636. end
  637. end
  638. # Allows you to add attachments to an email, like so:
  639. #
  640. # mail.attachments['filename.jpg'] = File.read('/path/to/filename.jpg')
  641. #
  642. # If you do this, then Mail will take the file name and work out the mime type.
  643. # It will also set the Content-Type, Content-Disposition, Content-Transfer-Encoding
  644. # and encode the contents of the attachment in Base64.
  645. #
  646. # You can also specify overrides if you want by passing a hash instead of a string:
  647. #
  648. # mail.attachments['filename.jpg'] = {mime_type: 'application/gzip',
  649. # content: File.read('/path/to/filename.jpg')}
  650. #
  651. # If you want to use encoding other than Base64 then you will need to pass encoding
  652. # type along with the pre-encoded content as Mail doesn't know how to decode the
  653. # data:
  654. #
  655. # file_content = SpecialEncode(File.read('/path/to/filename.jpg'))
  656. # mail.attachments['filename.jpg'] = {mime_type: 'application/gzip',
  657. # encoding: 'SpecialEncoding',
  658. # content: file_content }
  659. #
  660. # You can also search for specific attachments:
  661. #
  662. # # By Filename
  663. # mail.attachments['filename.jpg'] # => Mail::Part object or nil
  664. #
  665. # # or by index
  666. # mail.attachments[0] # => Mail::Part (first attachment)
  667. #
  668. 1 def attachments
  669. if @_mail_was_called
  670. LateAttachmentsProxy.new(@_message.attachments)
  671. else
  672. @_message.attachments
  673. end
  674. end
  675. 1 class LateAttachmentsProxy < SimpleDelegator
  676. 1 def inline; _raise_error end
  677. 1 def []=(_name, _content); _raise_error end
  678. 1 private
  679. 1 def _raise_error
  680. raise RuntimeError, "Can't add attachments after `mail` was called.\n" \
  681. "Make sure to use `attachments[]=` before calling `mail`."
  682. end
  683. end
  684. # The main method that creates the message and renders the email templates. There are
  685. # two ways to call this method, with a block, or without a block.
  686. #
  687. # It accepts a headers hash. This hash allows you to specify
  688. # the most used headers in an email message, these are:
  689. #
  690. # * +:subject+ - The subject of the message, if this is omitted, Action Mailer will
  691. # ask the Rails I18n class for a translated +:subject+ in the scope of
  692. # <tt>[mailer_scope, action_name]</tt> or if this is missing, will translate the
  693. # humanized version of the +action_name+
  694. # * +:to+ - Who the message is destined for, can be a string of addresses, or an array
  695. # of addresses.
  696. # * +:from+ - Who the message is from
  697. # * +:cc+ - Who you would like to Carbon-Copy on this email, can be a string of addresses,
  698. # or an array of addresses.
  699. # * +:bcc+ - Who you would like to Blind-Carbon-Copy on this email, can be a string of
  700. # addresses, or an array of addresses.
  701. # * +:reply_to+ - Who to set the Reply-To header of the email to.
  702. # * +:date+ - The date to say the email was sent on.
  703. #
  704. # You can set default values for any of the above headers (except +:date+)
  705. # by using the ::default class method:
  706. #
  707. # class Notifier < ActionMailer::Base
  708. # default from: 'no-reply@test.lindsaar.net',
  709. # bcc: 'email_logger@test.lindsaar.net',
  710. # reply_to: 'bounces@test.lindsaar.net'
  711. # end
  712. #
  713. # If you need other headers not listed above, you can either pass them in
  714. # as part of the headers hash or use the <tt>headers['name'] = value</tt>
  715. # method.
  716. #
  717. # When a +:return_path+ is specified as header, that value will be used as
  718. # the 'envelope from' address for the Mail message. Setting this is useful
  719. # when you want delivery notifications sent to a different address than the
  720. # one in +:from+. Mail will actually use the +:return_path+ in preference
  721. # to the +:sender+ in preference to the +:from+ field for the 'envelope
  722. # from' value.
  723. #
  724. # If you do not pass a block to the +mail+ method, it will find all
  725. # templates in the view paths using by default the mailer name and the
  726. # method name that it is being called from, it will then create parts for
  727. # each of these templates intelligently, making educated guesses on correct
  728. # content type and sequence, and return a fully prepared <tt>Mail::Message</tt>
  729. # ready to call <tt>:deliver</tt> on to send.
  730. #
  731. # For example:
  732. #
  733. # class Notifier < ActionMailer::Base
  734. # default from: 'no-reply@test.lindsaar.net'
  735. #
  736. # def welcome
  737. # mail(to: 'mikel@test.lindsaar.net')
  738. # end
  739. # end
  740. #
  741. # Will look for all templates at "app/views/notifier" with name "welcome".
  742. # If no welcome template exists, it will raise an ActionView::MissingTemplate error.
  743. #
  744. # However, those can be customized:
  745. #
  746. # mail(template_path: 'notifications', template_name: 'another')
  747. #
  748. # And now it will look for all templates at "app/views/notifications" with name "another".
  749. #
  750. # If you do pass a block, you can render specific templates of your choice:
  751. #
  752. # mail(to: 'mikel@test.lindsaar.net') do |format|
  753. # format.text
  754. # format.html
  755. # end
  756. #
  757. # You can even render plain text directly without using a template:
  758. #
  759. # mail(to: 'mikel@test.lindsaar.net') do |format|
  760. # format.text { render plain: "Hello Mikel!" }
  761. # format.html { render html: "<h1>Hello Mikel!</h1>".html_safe }
  762. # end
  763. #
  764. # Which will render a +multipart/alternative+ email with +text/plain+ and
  765. # +text/html+ parts.
  766. #
  767. # The block syntax also allows you to customize the part headers if desired:
  768. #
  769. # mail(to: 'mikel@test.lindsaar.net') do |format|
  770. # format.text(content_transfer_encoding: "base64")
  771. # format.html
  772. # end
  773. #
  774. 1 def mail(headers = {}, &block)
  775. return message if @_mail_was_called && headers.blank? && !block
  776. # At the beginning, do not consider class default for content_type
  777. content_type = headers[:content_type]
  778. headers = apply_defaults(headers)
  779. # Apply charset at the beginning so all fields are properly quoted
  780. message.charset = charset = headers[:charset]
  781. # Set configure delivery behavior
  782. wrap_delivery_behavior!(headers[:delivery_method], headers[:delivery_method_options])
  783. assign_headers_to_message(message, headers)
  784. # Render the templates and blocks
  785. responses = collect_responses(headers, &block)
  786. @_mail_was_called = true
  787. create_parts_from_responses(message, responses)
  788. # Setup content type, reapply charset and handle parts order
  789. message.content_type = set_content_type(message, content_type, headers[:content_type])
  790. message.charset = charset
  791. if message.multipart?
  792. message.body.set_sort_order(headers[:parts_order])
  793. message.body.sort_parts!
  794. end
  795. message
  796. end
  797. 1 private
  798. # Used by #mail to set the content type of the message.
  799. #
  800. # It will use the given +user_content_type+, or multipart if the mail
  801. # message has any attachments. If the attachments are inline, the content
  802. # type will be "multipart/related", otherwise "multipart/mixed".
  803. #
  804. # If there is no content type passed in via headers, and there are no
  805. # attachments, or the message is multipart, then the default content type is
  806. # used.
  807. 1 def set_content_type(m, user_content_type, class_default) # :doc:
  808. params = m.content_type_parameters || {}
  809. case
  810. when user_content_type.present?
  811. user_content_type
  812. when m.has_attachments?
  813. if m.attachments.detect(&:inline?)
  814. ["multipart", "related", params]
  815. else
  816. ["multipart", "mixed", params]
  817. end
  818. when m.multipart?
  819. ["multipart", "alternative", params]
  820. else
  821. m.content_type || class_default
  822. end
  823. end
  824. # Translates the +subject+ using Rails I18n class under <tt>[mailer_scope, action_name]</tt> scope.
  825. # If it does not find a translation for the +subject+ under the specified scope it will default to a
  826. # humanized version of the <tt>action_name</tt>.
  827. # If the subject has interpolations, you can pass them through the +interpolations+ parameter.
  828. 1 def default_i18n_subject(interpolations = {}) # :doc:
  829. mailer_scope = self.class.mailer_name.tr("/", ".")
  830. I18n.t(:subject, interpolations.merge(scope: [mailer_scope, action_name], default: action_name.humanize))
  831. end
  832. # Emails do not support relative path links.
  833. 1 def self.supports_path? # :doc:
  834. false
  835. end
  836. 1 def apply_defaults(headers)
  837. default_values = self.class.default.map do |key, value|
  838. [
  839. key,
  840. compute_default(value)
  841. ]
  842. end.to_h
  843. headers_with_defaults = headers.reverse_merge(default_values)
  844. headers_with_defaults[:subject] ||= default_i18n_subject
  845. headers_with_defaults
  846. end
  847. 1 def compute_default(value)
  848. return value unless value.is_a?(Proc)
  849. if value.arity == 1
  850. instance_exec(self, &value)
  851. else
  852. instance_exec(&value)
  853. end
  854. end
  855. 1 def assign_headers_to_message(message, headers)
  856. assignable = headers.except(:parts_order, :content_type, :body, :template_name,
  857. :template_path, :delivery_method, :delivery_method_options)
  858. assignable.each { |k, v| message[k] = v }
  859. end
  860. 1 def collect_responses(headers)
  861. if block_given?
  862. collector = ActionMailer::Collector.new(lookup_context) { render(action_name) }
  863. yield(collector)
  864. collector.responses
  865. elsif headers[:body]
  866. collect_responses_from_text(headers)
  867. else
  868. collect_responses_from_templates(headers)
  869. end
  870. end
  871. 1 def collect_responses_from_text(headers)
  872. [{
  873. body: headers.delete(:body),
  874. content_type: headers[:content_type] || "text/plain"
  875. }]
  876. end
  877. 1 def collect_responses_from_templates(headers)
  878. templates_path = headers[:template_path] || self.class.mailer_name
  879. templates_name = headers[:template_name] || action_name
  880. each_template(Array(templates_path), templates_name).map do |template|
  881. self.formats = template.formats
  882. {
  883. body: render(template: template),
  884. content_type: template.type.to_s
  885. }
  886. end
  887. end
  888. 1 def each_template(paths, name, &block)
  889. templates = lookup_context.find_all(name, paths)
  890. if templates.empty?
  891. raise ActionView::MissingTemplate.new(paths, name, paths, false, "mailer")
  892. else
  893. templates.uniq(&:formats).each(&block)
  894. end
  895. end
  896. 1 def create_parts_from_responses(m, responses)
  897. if responses.size == 1 && !m.has_attachments?
  898. responses[0].each { |k, v| m[k] = v }
  899. elsif responses.size > 1 && m.has_attachments?
  900. container = Mail::Part.new
  901. container.content_type = "multipart/alternative"
  902. responses.each { |r| insert_part(container, r, m.charset) }
  903. m.add_part(container)
  904. else
  905. responses.each { |r| insert_part(m, r, m.charset) }
  906. end
  907. end
  908. 1 def insert_part(container, response, charset)
  909. response[:charset] ||= charset
  910. part = Mail::Part.new(response)
  911. container.add_part(part)
  912. end
  913. # This and #instrument_name is for caching instrument
  914. 1 def instrument_payload(key)
  915. {
  916. mailer: mailer_name,
  917. key: key
  918. }
  919. end
  920. 1 def instrument_name
  921. "action_mailer".freeze
  922. end
  923. 1 ActiveSupport.run_load_hooks(:action_mailer, self)
  924. end
  925. end

target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/collector.rb

52.38% lines covered

21 relevant lines. 11 lines covered and 10 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "abstract_controller/collector"
  3. 1 require "active_support/core_ext/hash/reverse_merge"
  4. 1 require "active_support/core_ext/array/extract_options"
  5. 1 module ActionMailer
  6. 1 class Collector
  7. 1 include AbstractController::Collector
  8. 1 attr_reader :responses
  9. 1 def initialize(context, &block)
  10. @context = context
  11. @responses = []
  12. @default_render = block
  13. end
  14. 1 def any(*args, &block)
  15. options = args.extract_options!
  16. raise ArgumentError, "You have to supply at least one format" if args.empty?
  17. args.each { |type| send(type, options.dup, &block) }
  18. end
  19. 1 alias :all :any
  20. 1 def custom(mime, options = {})
  21. options.reverse_merge!(content_type: mime.to_s)
  22. @context.formats = [mime.to_sym]
  23. options[:body] = block_given? ? yield : @default_render.call
  24. @responses << options
  25. end
  26. end
  27. end

target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/delivery_job.rb

60.0% lines covered

15 relevant lines. 9 lines covered and 6 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_job"
  3. 1 module ActionMailer
  4. # The <tt>ActionMailer::DeliveryJob</tt> class is used when you
  5. # want to send emails outside of the request-response cycle.
  6. #
  7. # Exceptions are rescued and handled by the mailer class.
  8. 1 class DeliveryJob < ActiveJob::Base # :nodoc:
  9. 1 queue_as { ActionMailer::Base.deliver_later_queue_name }
  10. 1 rescue_from StandardError, with: :handle_exception_with_mailer_class
  11. 1 def perform(mailer, mail_method, delivery_method, *args) #:nodoc:
  12. mailer.constantize.public_send(mail_method, *args).send(delivery_method)
  13. end
  14. 1 private
  15. # "Deserialize" the mailer class name by hand in case another argument
  16. # (like a Global ID reference) raised DeserializationError.
  17. 1 def mailer_class
  18. if mailer = Array(@serialized_arguments).first || Array(arguments).first
  19. mailer.constantize
  20. end
  21. end
  22. 1 def handle_exception_with_mailer_class(exception)
  23. if klass = mailer_class
  24. klass.handle_exception exception
  25. else
  26. raise exception
  27. end
  28. end
  29. end
  30. end

target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/delivery_methods.rb

67.65% lines covered

34 relevant lines. 23 lines covered and 11 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "tmpdir"
  3. 1 module ActionMailer
  4. # This module handles everything related to mail delivery, from registering
  5. # new delivery methods to configuring the mail object to be sent.
  6. 1 module DeliveryMethods
  7. 1 extend ActiveSupport::Concern
  8. 1 included do
  9. # Do not make this inheritable, because we always want it to propagate
  10. 1 cattr_accessor :raise_delivery_errors, default: true
  11. 1 cattr_accessor :perform_deliveries, default: true
  12. 1 cattr_accessor :deliver_later_queue_name, default: :mailers
  13. 1 class_attribute :delivery_methods, default: {}.freeze
  14. 1 class_attribute :delivery_method, default: :smtp
  15. 1 add_delivery_method :smtp, Mail::SMTP,
  16. address: "localhost",
  17. port: 25,
  18. domain: "localhost.localdomain",
  19. user_name: nil,
  20. password: nil,
  21. authentication: nil,
  22. enable_starttls_auto: true
  23. 2 add_delivery_method :file, Mail::FileDelivery,
  24. 1 location: defined?(Rails.root) ? "#{Rails.root}/tmp/mails" : "#{Dir.tmpdir}/mails"
  25. 1 add_delivery_method :sendmail, Mail::Sendmail,
  26. location: "/usr/sbin/sendmail",
  27. arguments: "-i"
  28. 1 add_delivery_method :test, Mail::TestMailer
  29. end
  30. # Helpers for creating and wrapping delivery behavior, used by DeliveryMethods.
  31. 1 module ClassMethods
  32. # Provides a list of emails that have been delivered by Mail::TestMailer
  33. 1 delegate :deliveries, :deliveries=, to: Mail::TestMailer
  34. # Adds a new delivery method through the given class using the given
  35. # symbol as alias and the default options supplied.
  36. #
  37. # add_delivery_method :sendmail, Mail::Sendmail,
  38. # location: '/usr/sbin/sendmail',
  39. # arguments: '-i'
  40. 1 def add_delivery_method(symbol, klass, default_options = {})
  41. 4 class_attribute(:"#{symbol}_settings") unless respond_to?(:"#{symbol}_settings")
  42. 4 send(:"#{symbol}_settings=", default_options)
  43. 4 self.delivery_methods = delivery_methods.merge(symbol.to_sym => klass).freeze
  44. end
  45. 1 def wrap_delivery_behavior(mail, method = nil, options = nil) # :nodoc:
  46. method ||= delivery_method
  47. mail.delivery_handler = self
  48. case method
  49. when NilClass
  50. raise "Delivery method cannot be nil"
  51. when Symbol
  52. if klass = delivery_methods[method]
  53. mail.delivery_method(klass, (send(:"#{method}_settings") || {}).merge(options || {}))
  54. else
  55. raise "Invalid delivery method #{method.inspect}"
  56. end
  57. else
  58. mail.delivery_method(method)
  59. end
  60. mail.perform_deliveries = perform_deliveries
  61. mail.raise_delivery_errors = raise_delivery_errors
  62. end
  63. end
  64. 1 def wrap_delivery_behavior!(*args) # :nodoc:
  65. self.class.wrap_delivery_behavior(message, *args)
  66. end
  67. end
  68. end

target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/inline_preview_interceptor.rb

52.0% lines covered

25 relevant lines. 13 lines covered and 12 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "base64"
  3. 1 module ActionMailer
  4. # Implements a mailer preview interceptor that converts image tag src attributes
  5. # that use inline cid: style URLs to data: style URLs so that they are visible
  6. # when previewing an HTML email in a web browser.
  7. #
  8. # This interceptor is enabled by default. To disable it, delete it from the
  9. # <tt>ActionMailer::Base.preview_interceptors</tt> array:
  10. #
  11. # ActionMailer::Base.preview_interceptors.delete(ActionMailer::InlinePreviewInterceptor)
  12. #
  13. 1 class InlinePreviewInterceptor
  14. 1 PATTERN = /src=(?:"cid:[^"]+"|'cid:[^']+')/i
  15. 1 include Base64
  16. 1 def self.previewing_email(message) #:nodoc:
  17. new(message).transform!
  18. end
  19. 1 def initialize(message) #:nodoc:
  20. @message = message
  21. end
  22. 1 def transform! #:nodoc:
  23. return message if html_part.blank?
  24. html_part.body = html_part.decoded.gsub(PATTERN) do |match|
  25. if part = find_part(match[9..-2])
  26. %[src="#{data_url(part)}"]
  27. else
  28. match
  29. end
  30. end
  31. message
  32. end
  33. 1 private
  34. 1 def message
  35. @message
  36. end
  37. 1 def html_part
  38. @html_part ||= message.html_part
  39. end
  40. 1 def data_url(part)
  41. "data:#{part.mime_type};base64,#{strict_encode64(part.body.raw_source)}"
  42. end
  43. 1 def find_part(cid)
  44. message.all_parts.find { |p| p.attachment? && p.cid == cid }
  45. end
  46. end
  47. end

target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/log_subscriber.rb

42.11% lines covered

19 relevant lines. 8 lines covered and 11 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/log_subscriber"
  3. 1 module ActionMailer
  4. # Implements the ActiveSupport::LogSubscriber for logging notifications when
  5. # email is delivered or received.
  6. 1 class LogSubscriber < ActiveSupport::LogSubscriber
  7. # An email was delivered.
  8. 1 def deliver(event)
  9. info do
  10. recipients = Array(event.payload[:to]).join(", ")
  11. "Sent mail to #{recipients} (#{event.duration.round(1)}ms)"
  12. end
  13. debug { event.payload[:mail] }
  14. end
  15. # An email was received.
  16. 1 def receive(event)
  17. info { "Received mail (#{event.duration.round(1)}ms)" }
  18. debug { event.payload[:mail] }
  19. end
  20. # An email was generated.
  21. 1 def process(event)
  22. debug do
  23. mailer = event.payload[:mailer]
  24. action = event.payload[:action]
  25. "#{mailer}##{action}: processed outbound mail in #{event.duration.round(1)}ms"
  26. end
  27. end
  28. # Use the logger configured for ActionMailer::Base.
  29. 1 def logger
  30. ActionMailer::Base.logger
  31. end
  32. end
  33. end
  34. 1 ActionMailer::LogSubscriber.attach_to :action_mailer

target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/mail_helper.rb

30.43% lines covered

23 relevant lines. 7 lines covered and 16 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActionMailer
  3. # Provides helper methods for ActionMailer::Base that can be used for easily
  4. # formatting messages, accessing mailer or message instances, and the
  5. # attachments list.
  6. 1 module MailHelper
  7. # Take the text and format it, indented two spaces for each line, and
  8. # wrapped at 72 columns:
  9. #
  10. # text = <<-TEXT
  11. # This is
  12. # the paragraph.
  13. #
  14. # * item1 * item2
  15. # TEXT
  16. #
  17. # block_format text
  18. # # => " This is the paragraph.\n\n * item1\n * item2\n"
  19. 1 def block_format(text)
  20. formatted = text.split(/\n\r?\n/).collect { |paragraph|
  21. format_paragraph(paragraph)
  22. }.join("\n\n")
  23. # Make list points stand on their own line
  24. formatted.gsub!(/[ ]*([*]+) ([^*]*)/) { " #{$1} #{$2.strip}\n" }
  25. formatted.gsub!(/[ ]*([#]+) ([^#]*)/) { " #{$1} #{$2.strip}\n" }
  26. formatted
  27. end
  28. # Access the mailer instance.
  29. 1 def mailer
  30. @_controller
  31. end
  32. # Access the message instance.
  33. 1 def message
  34. @_message
  35. end
  36. # Access the message attachments list.
  37. 1 def attachments
  38. mailer.attachments
  39. end
  40. # Returns +text+ wrapped at +len+ columns and indented +indent+ spaces.
  41. # By default column length +len+ equals 72 characters and indent
  42. # +indent+ equal two spaces.
  43. #
  44. # my_text = 'Here is a sample text with more than 40 characters'
  45. #
  46. # format_paragraph(my_text, 25, 4)
  47. # # => " Here is a sample text with\n more than 40 characters"
  48. 1 def format_paragraph(text, len = 72, indent = 2)
  49. sentences = [[]]
  50. text.split.each do |word|
  51. if sentences.first.present? && (sentences.last + [word]).join(" ").length > len
  52. sentences << [word]
  53. else
  54. sentences.last << word
  55. end
  56. end
  57. indentation = " " * indent
  58. sentences.map! { |sentence|
  59. "#{indentation}#{sentence.join(' ')}"
  60. }.join "\n"
  61. end
  62. end
  63. end

target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/message_delivery.rb

42.86% lines covered

35 relevant lines. 15 lines covered and 20 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "delegate"
  3. 1 module ActionMailer
  4. # The <tt>ActionMailer::MessageDelivery</tt> class is used by
  5. # ActionMailer::Base when creating a new mailer.
  6. # <tt>MessageDelivery</tt> is a wrapper (+Delegator+ subclass) around a lazy
  7. # created <tt>Mail::Message</tt>. You can get direct access to the
  8. # <tt>Mail::Message</tt>, deliver the email or schedule the email to be sent
  9. # through Active Job.
  10. #
  11. # Notifier.welcome(User.first) # an ActionMailer::MessageDelivery object
  12. # Notifier.welcome(User.first).deliver_now # sends the email
  13. # Notifier.welcome(User.first).deliver_later # enqueue email delivery as a job through Active Job
  14. # Notifier.welcome(User.first).message # a Mail::Message object
  15. 1 class MessageDelivery < Delegator
  16. 1 def initialize(mailer_class, action, *args) #:nodoc:
  17. @mailer_class, @action, @args = mailer_class, action, args
  18. # The mail is only processed if we try to call any methods on it.
  19. # Typical usage will leave it unloaded and call deliver_later.
  20. @processed_mailer = nil
  21. @mail_message = nil
  22. end
  23. # Method calls are delegated to the Mail::Message that's ready to deliver.
  24. 1 def __getobj__ #:nodoc:
  25. @mail_message ||= processed_mailer.message
  26. end
  27. # Unused except for delegator internals (dup, marshaling).
  28. 1 def __setobj__(mail_message) #:nodoc:
  29. @mail_message = mail_message
  30. end
  31. # Returns the resulting Mail::Message
  32. 1 def message
  33. __getobj__
  34. end
  35. # Was the delegate loaded, causing the mailer action to be processed?
  36. 1 def processed?
  37. @processed_mailer || @mail_message
  38. end
  39. # Enqueues the email to be delivered through Active Job. When the
  40. # job runs it will send the email using +deliver_now!+. That means
  41. # that the message will be sent bypassing checking +perform_deliveries+
  42. # and +raise_delivery_errors+, so use with caution.
  43. #
  44. # Notifier.welcome(User.first).deliver_later!
  45. # Notifier.welcome(User.first).deliver_later!(wait: 1.hour)
  46. # Notifier.welcome(User.first).deliver_later!(wait_until: 10.hours.from_now)
  47. #
  48. # Options:
  49. #
  50. # * <tt>:wait</tt> - Enqueue the email to be delivered with a delay
  51. # * <tt>:wait_until</tt> - Enqueue the email to be delivered at (after) a specific date / time
  52. # * <tt>:queue</tt> - Enqueue the email on the specified queue
  53. #
  54. # By default, the email will be enqueued using <tt>ActionMailer::DeliveryJob</tt>. Each
  55. # <tt>ActionMailer::Base</tt> class can specify the job to use by setting the class variable
  56. # +delivery_job+.
  57. #
  58. # class AccountRegistrationMailer < ApplicationMailer
  59. # self.delivery_job = RegistrationDeliveryJob
  60. # end
  61. 1 def deliver_later!(options = {})
  62. enqueue_delivery :deliver_now!, options
  63. end
  64. # Enqueues the email to be delivered through Active Job. When the
  65. # job runs it will send the email using +deliver_now+.
  66. #
  67. # Notifier.welcome(User.first).deliver_later
  68. # Notifier.welcome(User.first).deliver_later(wait: 1.hour)
  69. # Notifier.welcome(User.first).deliver_later(wait_until: 10.hours.from_now)
  70. #
  71. # Options:
  72. #
  73. # * <tt>:wait</tt> - Enqueue the email to be delivered with a delay.
  74. # * <tt>:wait_until</tt> - Enqueue the email to be delivered at (after) a specific date / time.
  75. # * <tt>:queue</tt> - Enqueue the email on the specified queue.
  76. #
  77. # By default, the email will be enqueued using <tt>ActionMailer::DeliveryJob</tt>. Each
  78. # <tt>ActionMailer::Base</tt> class can specify the job to use by setting the class variable
  79. # +delivery_job+.
  80. #
  81. # class AccountRegistrationMailer < ApplicationMailer
  82. # self.delivery_job = RegistrationDeliveryJob
  83. # end
  84. 1 def deliver_later(options = {})
  85. enqueue_delivery :deliver_now, options
  86. end
  87. # Delivers an email without checking +perform_deliveries+ and +raise_delivery_errors+,
  88. # so use with caution.
  89. #
  90. # Notifier.welcome(User.first).deliver_now!
  91. #
  92. 1 def deliver_now!
  93. processed_mailer.handle_exceptions do
  94. message.deliver!
  95. end
  96. end
  97. # Delivers an email:
  98. #
  99. # Notifier.welcome(User.first).deliver_now
  100. #
  101. 1 def deliver_now
  102. processed_mailer.handle_exceptions do
  103. message.deliver
  104. end
  105. end
  106. 1 private
  107. # Returns the processed Mailer instance. We keep this instance
  108. # on hand so we can delegate exception handling to it.
  109. 1 def processed_mailer
  110. @processed_mailer ||= @mailer_class.new.tap do |mailer|
  111. mailer.process @action, *@args
  112. end
  113. end
  114. 1 def enqueue_delivery(delivery_method, options = {})
  115. if processed?
  116. ::Kernel.raise "You've accessed the message before asking to " \
  117. "deliver it later, so you may have made local changes that would " \
  118. "be silently lost if we enqueued a job to deliver it. Why? Only " \
  119. "the mailer method *arguments* are passed with the delivery job! " \
  120. "Do not access the message in any way if you mean to deliver it " \
  121. "later. Workarounds: 1. don't touch the message before calling " \
  122. "#deliver_later, 2. only touch the message *within your mailer " \
  123. "method*, or 3. use a custom Active Job instead of #deliver_later."
  124. else
  125. args = @mailer_class.name, @action.to_s, delivery_method.to_s, *@args
  126. job = @mailer_class.delivery_job
  127. job.set(options).perform_later(*args)
  128. end
  129. end
  130. end
  131. end

target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/parameterized.rb

54.29% lines covered

35 relevant lines. 19 lines covered and 16 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActionMailer
  3. # Provides the option to parameterize mailers in order to share instance variable
  4. # setup, processing, and common headers.
  5. #
  6. # Consider this example that does not use parameterization:
  7. #
  8. # class InvitationsMailer < ApplicationMailer
  9. # def account_invitation(inviter, invitee)
  10. # @account = inviter.account
  11. # @inviter = inviter
  12. # @invitee = invitee
  13. #
  14. # subject = "#{@inviter.name} invited you to their Basecamp (#{@account.name})"
  15. #
  16. # mail \
  17. # subject: subject,
  18. # to: invitee.email_address,
  19. # from: common_address(inviter),
  20. # reply_to: inviter.email_address_with_name
  21. # end
  22. #
  23. # def project_invitation(project, inviter, invitee)
  24. # @account = inviter.account
  25. # @project = project
  26. # @inviter = inviter
  27. # @invitee = invitee
  28. # @summarizer = ProjectInvitationSummarizer.new(@project.bucket)
  29. #
  30. # subject = "#{@inviter.name.familiar} added you to a project in Basecamp (#{@account.name})"
  31. #
  32. # mail \
  33. # subject: subject,
  34. # to: invitee.email_address,
  35. # from: common_address(inviter),
  36. # reply_to: inviter.email_address_with_name
  37. # end
  38. #
  39. # def bulk_project_invitation(projects, inviter, invitee)
  40. # @account = inviter.account
  41. # @projects = projects.sort_by(&:name)
  42. # @inviter = inviter
  43. # @invitee = invitee
  44. #
  45. # subject = "#{@inviter.name.familiar} added you to some new stuff in Basecamp (#{@account.name})"
  46. #
  47. # mail \
  48. # subject: subject,
  49. # to: invitee.email_address,
  50. # from: common_address(inviter),
  51. # reply_to: inviter.email_address_with_name
  52. # end
  53. # end
  54. #
  55. # InvitationsMailer.account_invitation(person_a, person_b).deliver_later
  56. #
  57. # Using parameterized mailers, this can be rewritten as:
  58. #
  59. # class InvitationsMailer < ApplicationMailer
  60. # before_action { @inviter, @invitee = params[:inviter], params[:invitee] }
  61. # before_action { @account = params[:inviter].account }
  62. #
  63. # default to: -> { @invitee.email_address },
  64. # from: -> { common_address(@inviter) },
  65. # reply_to: -> { @inviter.email_address_with_name }
  66. #
  67. # def account_invitation
  68. # mail subject: "#{@inviter.name} invited you to their Basecamp (#{@account.name})"
  69. # end
  70. #
  71. # def project_invitation
  72. # @project = params[:project]
  73. # @summarizer = ProjectInvitationSummarizer.new(@project.bucket)
  74. #
  75. # mail subject: "#{@inviter.name.familiar} added you to a project in Basecamp (#{@account.name})"
  76. # end
  77. #
  78. # def bulk_project_invitation
  79. # @projects = params[:projects].sort_by(&:name)
  80. #
  81. # mail subject: "#{@inviter.name.familiar} added you to some new stuff in Basecamp (#{@account.name})"
  82. # end
  83. # end
  84. #
  85. # InvitationsMailer.with(inviter: person_a, invitee: person_b).account_invitation.deliver_later
  86. 1 module Parameterized
  87. 1 extend ActiveSupport::Concern
  88. 1 included do
  89. 1 attr_accessor :params
  90. end
  91. 1 module ClassMethods
  92. # Provide the parameters to the mailer in order to use them in the instance methods and callbacks.
  93. #
  94. # InvitationsMailer.with(inviter: person_a, invitee: person_b).account_invitation.deliver_later
  95. #
  96. # See Parameterized documentation for full example.
  97. 1 def with(params)
  98. ActionMailer::Parameterized::Mailer.new(self, params)
  99. end
  100. end
  101. 1 class Mailer # :nodoc:
  102. 1 def initialize(mailer, params)
  103. @mailer, @params = mailer, params
  104. end
  105. 1 private
  106. 1 def method_missing(method_name, *args)
  107. if @mailer.action_methods.include?(method_name.to_s)
  108. ActionMailer::Parameterized::MessageDelivery.new(@mailer, method_name, @params, *args)
  109. else
  110. super
  111. end
  112. end
  113. 1 def respond_to_missing?(method, include_all = false)
  114. @mailer.respond_to?(method, include_all)
  115. end
  116. end
  117. 1 class MessageDelivery < ActionMailer::MessageDelivery # :nodoc:
  118. 1 def initialize(mailer_class, action, params, *args)
  119. super(mailer_class, action, *args)
  120. @params = params
  121. end
  122. 1 private
  123. 1 def processed_mailer
  124. @processed_mailer ||= @mailer_class.new.tap do |mailer|
  125. mailer.params = @params
  126. mailer.process @action, *@args
  127. end
  128. end
  129. 1 def enqueue_delivery(delivery_method, options = {})
  130. if processed?
  131. super
  132. else
  133. args = @mailer_class.name, @action.to_s, delivery_method.to_s, @params, *@args
  134. ActionMailer::Parameterized::DeliveryJob.set(options).perform_later(*args)
  135. end
  136. end
  137. end
  138. 1 class DeliveryJob < ActionMailer::DeliveryJob # :nodoc:
  139. 1 def perform(mailer, mail_method, delivery_method, params, *args)
  140. mailer.constantize.with(params).public_send(mail_method, *args).send(delivery_method)
  141. end
  142. end
  143. end
  144. end

target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/preview.rb

55.77% lines covered

52 relevant lines. 29 lines covered and 23 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/descendants_tracker"
  3. 1 module ActionMailer
  4. 1 module Previews #:nodoc:
  5. 1 extend ActiveSupport::Concern
  6. 1 included do
  7. # Set the location of mailer previews through app configuration:
  8. #
  9. # config.action_mailer.preview_path = "#{Rails.root}/lib/mailer_previews"
  10. #
  11. 1 mattr_accessor :preview_path, instance_writer: false
  12. # Enable or disable mailer previews through app configuration:
  13. #
  14. # config.action_mailer.show_previews = true
  15. #
  16. # Defaults to +true+ for development environment
  17. #
  18. 1 mattr_accessor :show_previews, instance_writer: false
  19. # :nodoc:
  20. 1 mattr_accessor :preview_interceptors, instance_writer: false, default: [ActionMailer::InlinePreviewInterceptor]
  21. end
  22. 1 module ClassMethods
  23. # Register one or more Interceptors which will be called before mail is previewed.
  24. 1 def register_preview_interceptors(*interceptors)
  25. 1 interceptors.flatten.compact.each { |interceptor| register_preview_interceptor(interceptor) }
  26. end
  27. # Register an Interceptor which will be called before mail is previewed.
  28. # Either a class or a string can be passed in as the Interceptor. If a
  29. # string is passed in it will be constantized.
  30. 1 def register_preview_interceptor(interceptor)
  31. preview_interceptor = \
  32. case interceptor
  33. when String, Symbol
  34. interceptor.to_s.camelize.constantize
  35. else
  36. interceptor
  37. end
  38. unless preview_interceptors.include?(preview_interceptor)
  39. preview_interceptors << preview_interceptor
  40. end
  41. end
  42. end
  43. end
  44. 1 class Preview
  45. 1 extend ActiveSupport::DescendantsTracker
  46. 1 attr_reader :params
  47. 1 def initialize(params = {})
  48. @params = params
  49. end
  50. 1 class << self
  51. # Returns all mailer preview classes.
  52. 1 def all
  53. load_previews if descendants.empty?
  54. descendants
  55. end
  56. # Returns the mail object for the given email name. The registered preview
  57. # interceptors will be informed so that they can transform the message
  58. # as they would if the mail was actually being delivered.
  59. 1 def call(email, params = {})
  60. preview = new(params)
  61. message = preview.public_send(email)
  62. inform_preview_interceptors(message)
  63. message
  64. end
  65. # Returns all of the available email previews.
  66. 1 def emails
  67. public_instance_methods(false).map(&:to_s).sort
  68. end
  69. # Returns +true+ if the email exists.
  70. 1 def email_exists?(email)
  71. emails.include?(email)
  72. end
  73. # Returns +true+ if the preview exists.
  74. 1 def exists?(preview)
  75. all.any? { |p| p.preview_name == preview }
  76. end
  77. # Find a mailer preview by its underscored class name.
  78. 1 def find(preview)
  79. all.find { |p| p.preview_name == preview }
  80. end
  81. # Returns the underscored name of the mailer preview without the suffix.
  82. 1 def preview_name
  83. name.sub(/Preview$/, "").underscore
  84. end
  85. 1 private
  86. 1 def load_previews
  87. if preview_path
  88. Dir["#{preview_path}/**/*_preview.rb"].sort.each { |file| require_dependency file }
  89. end
  90. end
  91. 1 def preview_path
  92. Base.preview_path
  93. end
  94. 1 def show_previews
  95. Base.show_previews
  96. end
  97. 1 def inform_preview_interceptors(message)
  98. Base.preview_interceptors.each do |interceptor|
  99. interceptor.previewing_email(message)
  100. end
  101. end
  102. end
  103. end
  104. end

target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/rescuable.rb

64.29% lines covered

14 relevant lines. 9 lines covered and 5 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActionMailer #:nodoc:
  3. # Provides +rescue_from+ for mailers. Wraps mailer action processing,
  4. # mail job processing, and mail delivery.
  5. 1 module Rescuable
  6. 1 extend ActiveSupport::Concern
  7. 1 include ActiveSupport::Rescuable
  8. 1 class_methods do
  9. 1 def handle_exception(exception) #:nodoc:
  10. rescue_with_handler(exception) || raise(exception)
  11. end
  12. end
  13. 1 def handle_exceptions #:nodoc:
  14. yield
  15. rescue => exception
  16. rescue_with_handler(exception) || raise
  17. end
  18. 1 private
  19. 1 def process(*)
  20. handle_exceptions do
  21. super
  22. end
  23. end
  24. end
  25. end

target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/test_case.rb

57.97% lines covered

69 relevant lines. 40 lines covered and 29 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/test_case"
  3. 1 require "rails-dom-testing"
  4. 1 module ActionMailer
  5. 1 class NonInferrableMailerError < ::StandardError
  6. 1 def initialize(name)
  7. super "Unable to determine the mailer to test from #{name}. " \
  8. "You'll need to specify it using tests YourMailer in your " \
  9. "test case definition"
  10. end
  11. end
  12. 1 class TestCase < ActiveSupport::TestCase
  13. 1 module ClearTestDeliveries
  14. 1 extend ActiveSupport::Concern
  15. 1 included do
  16. 1 setup :clear_test_deliveries
  17. 1 teardown :clear_test_deliveries
  18. end
  19. 1 private
  20. 1 def clear_test_deliveries
  21. 4 if ActionMailer::Base.delivery_method == :test
  22. ActionMailer::Base.deliveries.clear
  23. end
  24. end
  25. end
  26. 1 module Behavior
  27. 1 extend ActiveSupport::Concern
  28. 1 include ActiveSupport::Testing::ConstantLookup
  29. 1 include TestHelper
  30. 1 include Rails::Dom::Testing::Assertions::SelectorAssertions
  31. 1 include Rails::Dom::Testing::Assertions::DomAssertions
  32. 1 included do
  33. 1 class_attribute :_mailer_class
  34. 1 setup :initialize_test_deliveries
  35. 1 setup :set_expected_mail
  36. 1 teardown :restore_test_deliveries
  37. 1 ActiveSupport.run_load_hooks(:action_mailer_test_case, self)
  38. end
  39. 1 module ClassMethods
  40. 1 def tests(mailer)
  41. case mailer
  42. when String, Symbol
  43. self._mailer_class = mailer.to_s.camelize.constantize
  44. when Module
  45. self._mailer_class = mailer
  46. else
  47. raise NonInferrableMailerError.new(mailer)
  48. end
  49. end
  50. 1 def mailer_class
  51. if mailer = _mailer_class
  52. mailer
  53. else
  54. tests determine_default_mailer(name)
  55. end
  56. end
  57. 1 def determine_default_mailer(name)
  58. mailer = determine_constant_from_test_name(name) do |constant|
  59. Class === constant && constant < ActionMailer::Base
  60. end
  61. raise NonInferrableMailerError.new(name) if mailer.nil?
  62. mailer
  63. end
  64. end
  65. 1 private
  66. 1 def initialize_test_deliveries
  67. set_delivery_method :test
  68. @old_perform_deliveries = ActionMailer::Base.perform_deliveries
  69. ActionMailer::Base.perform_deliveries = true
  70. ActionMailer::Base.deliveries.clear
  71. end
  72. 1 def restore_test_deliveries
  73. restore_delivery_method
  74. ActionMailer::Base.perform_deliveries = @old_perform_deliveries
  75. end
  76. 1 def set_delivery_method(method)
  77. @old_delivery_method = ActionMailer::Base.delivery_method
  78. ActionMailer::Base.delivery_method = method
  79. end
  80. 1 def restore_delivery_method
  81. ActionMailer::Base.deliveries.clear
  82. ActionMailer::Base.delivery_method = @old_delivery_method
  83. end
  84. 1 def set_expected_mail
  85. @expected = Mail.new
  86. @expected.content_type ["text", "plain", { "charset" => charset }]
  87. @expected.mime_version = "1.0"
  88. end
  89. 1 def charset
  90. "UTF-8"
  91. end
  92. 1 def encode(subject)
  93. Mail::Encodings.q_value_encode(subject, charset)
  94. end
  95. 1 def read_fixture(action)
  96. IO.readlines(File.join(Rails.root, "test", "fixtures", self.class.mailer_class.name.underscore, action))
  97. end
  98. end
  99. 1 include Behavior
  100. end
  101. end

target/rubygems/gems/actionmailer-5.2.3/lib/action_mailer/test_helper.rb

37.5% lines covered

24 relevant lines. 9 lines covered and 15 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_job"
  3. 1 module ActionMailer
  4. # Provides helper methods for testing Action Mailer, including #assert_emails
  5. # and #assert_no_emails.
  6. 1 module TestHelper
  7. 1 include ActiveJob::TestHelper
  8. # Asserts that the number of emails sent matches the given number.
  9. #
  10. # def test_emails
  11. # assert_emails 0
  12. # ContactMailer.welcome.deliver_now
  13. # assert_emails 1
  14. # ContactMailer.welcome.deliver_now
  15. # assert_emails 2
  16. # end
  17. #
  18. # If a block is passed, that block should cause the specified number of
  19. # emails to be sent.
  20. #
  21. # def test_emails_again
  22. # assert_emails 1 do
  23. # ContactMailer.welcome.deliver_now
  24. # end
  25. #
  26. # assert_emails 2 do
  27. # ContactMailer.welcome.deliver_now
  28. # ContactMailer.welcome.deliver_now
  29. # end
  30. # end
  31. 1 def assert_emails(number)
  32. if block_given?
  33. original_count = ActionMailer::Base.deliveries.size
  34. yield
  35. new_count = ActionMailer::Base.deliveries.size
  36. assert_equal number, new_count - original_count, "#{number} emails expected, but #{new_count - original_count} were sent"
  37. else
  38. assert_equal number, ActionMailer::Base.deliveries.size
  39. end
  40. end
  41. # Asserts that no emails have been sent.
  42. #
  43. # def test_emails
  44. # assert_no_emails
  45. # ContactMailer.welcome.deliver_now
  46. # assert_emails 1
  47. # end
  48. #
  49. # If a block is passed, that block should not cause any emails to be sent.
  50. #
  51. # def test_emails_again
  52. # assert_no_emails do
  53. # # No emails should be sent from this block
  54. # end
  55. # end
  56. #
  57. # Note: This assertion is simply a shortcut for:
  58. #
  59. # assert_emails 0, &block
  60. 1 def assert_no_emails(&block)
  61. assert_emails 0, &block
  62. end
  63. # Asserts that the number of emails enqueued for later delivery matches
  64. # the given number.
  65. #
  66. # def test_emails
  67. # assert_enqueued_emails 0
  68. # ContactMailer.welcome.deliver_later
  69. # assert_enqueued_emails 1
  70. # ContactMailer.welcome.deliver_later
  71. # assert_enqueued_emails 2
  72. # end
  73. #
  74. # If a block is passed, that block should cause the specified number of
  75. # emails to be enqueued.
  76. #
  77. # def test_emails_again
  78. # assert_enqueued_emails 1 do
  79. # ContactMailer.welcome.deliver_later
  80. # end
  81. #
  82. # assert_enqueued_emails 2 do
  83. # ContactMailer.welcome.deliver_later
  84. # ContactMailer.welcome.deliver_later
  85. # end
  86. # end
  87. 1 def assert_enqueued_emails(number, &block)
  88. assert_enqueued_jobs number, only: [ ActionMailer::DeliveryJob, ActionMailer::Parameterized::DeliveryJob ], &block
  89. end
  90. # Asserts that block should cause the specified email
  91. # to be enqueued.
  92. #
  93. # def test_email_in_block
  94. # assert_enqueued_email_with ContactMailer, :welcome do
  95. # ContactMailer.welcome.deliver_later
  96. # end
  97. # end
  98. #
  99. # If +args+ is provided as a Hash, a parameterized email is matched.
  100. #
  101. # def test_parameterized_email
  102. # assert_enqueued_email_with ContactMailer, :welcome,
  103. # args: {email: 'user@example.com} do
  104. # ContactMailer.with(email: 'user@example.com').welcome.deliver_later
  105. # end
  106. # end
  107. 1 def assert_enqueued_email_with(mailer, method, args: nil, queue: "mailers", &block)
  108. if args.is_a? Hash
  109. job = ActionMailer::Parameterized::DeliveryJob
  110. args = [mailer.to_s, method.to_s, "deliver_now", args]
  111. else
  112. job = ActionMailer::DeliveryJob
  113. args = [mailer.to_s, method.to_s, "deliver_now", *args]
  114. end
  115. assert_enqueued_with(job: job, args: args, queue: queue, &block)
  116. end
  117. # Asserts that no emails are enqueued for later delivery.
  118. #
  119. # def test_no_emails
  120. # assert_no_enqueued_emails
  121. # ContactMailer.welcome.deliver_later
  122. # assert_enqueued_emails 1
  123. # end
  124. #
  125. # If a block is provided, it should not cause any emails to be enqueued.
  126. #
  127. # def test_no_emails
  128. # assert_no_enqueued_emails do
  129. # # No emails should be enqueued from this block
  130. # end
  131. # end
  132. 1 def assert_no_enqueued_emails(&block)
  133. assert_no_enqueued_jobs only: [ ActionMailer::DeliveryJob, ActionMailer::Parameterized::DeliveryJob ], &block
  134. end
  135. end
  136. end

target/rubygems/gems/actionpack-5.2.3/lib/action_controller/api.rb

80.0% lines covered

15 relevant lines. 12 lines covered and 3 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "action_view"
  3. 1 require "action_controller"
  4. 1 require "action_controller/log_subscriber"
  5. 1 module ActionController
  6. # API Controller is a lightweight version of <tt>ActionController::Base</tt>,
  7. # created for applications that don't require all functionalities that a complete
  8. # \Rails controller provides, allowing you to create controllers with just the
  9. # features that you need for API only applications.
  10. #
  11. # An API Controller is different from a normal controller in the sense that
  12. # by default it doesn't include a number of features that are usually required
  13. # by browser access only: layouts and templates rendering, cookies, sessions,
  14. # flash, assets, and so on. This makes the entire controller stack thinner,
  15. # suitable for API applications. It doesn't mean you won't have such
  16. # features if you need them: they're all available for you to include in
  17. # your application, they're just not part of the default API controller stack.
  18. #
  19. # Normally, +ApplicationController+ is the only controller that inherits from
  20. # <tt>ActionController::API</tt>. All other controllers in turn inherit from
  21. # +ApplicationController+.
  22. #
  23. # A sample controller could look like this:
  24. #
  25. # class PostsController < ApplicationController
  26. # def index
  27. # posts = Post.all
  28. # render json: posts
  29. # end
  30. # end
  31. #
  32. # Request, response, and parameters objects all work the exact same way as
  33. # <tt>ActionController::Base</tt>.
  34. #
  35. # == Renders
  36. #
  37. # The default API Controller stack includes all renderers, which means you
  38. # can use <tt>render :json</tt> and brothers freely in your controllers. Keep
  39. # in mind that templates are not going to be rendered, so you need to ensure
  40. # your controller is calling either <tt>render</tt> or <tt>redirect_to</tt> in
  41. # all actions, otherwise it will return 204 No Content.
  42. #
  43. # def show
  44. # post = Post.find(params[:id])
  45. # render json: post
  46. # end
  47. #
  48. # == Redirects
  49. #
  50. # Redirects are used to move from one action to another. You can use the
  51. # <tt>redirect_to</tt> method in your controllers in the same way as in
  52. # <tt>ActionController::Base</tt>. For example:
  53. #
  54. # def create
  55. # redirect_to root_url and return if not_authorized?
  56. # # do stuff here
  57. # end
  58. #
  59. # == Adding New Behavior
  60. #
  61. # In some scenarios you may want to add back some functionality provided by
  62. # <tt>ActionController::Base</tt> that is not present by default in
  63. # <tt>ActionController::API</tt>, for instance <tt>MimeResponds</tt>. This
  64. # module gives you the <tt>respond_to</tt> method. Adding it is quite simple,
  65. # you just need to include the module in a specific controller or in
  66. # +ApplicationController+ in case you want it available in your entire
  67. # application:
  68. #
  69. # class ApplicationController < ActionController::API
  70. # include ActionController::MimeResponds
  71. # end
  72. #
  73. # class PostsController < ApplicationController
  74. # def index
  75. # posts = Post.all
  76. #
  77. # respond_to do |format|
  78. # format.json { render json: posts }
  79. # format.xml { render xml: posts }
  80. # end
  81. # end
  82. # end
  83. #
  84. # Make sure to check the modules included in <tt>ActionController::Base</tt>
  85. # if you want to use any other functionality that is not provided
  86. # by <tt>ActionController::API</tt> out of the box.
  87. 1 class API < Metal
  88. 1 abstract!
  89. # Shortcut helper that returns all the ActionController::API modules except
  90. # the ones passed as arguments:
  91. #
  92. # class MyAPIBaseController < ActionController::Metal
  93. # ActionController::API.without_modules(:ForceSSL, :UrlFor).each do |left|
  94. # include left
  95. # end
  96. # end
  97. #
  98. # This gives better control over what you want to exclude and makes it easier
  99. # to create an API controller class, instead of listing the modules required
  100. # manually.
  101. 1 def self.without_modules(*modules)
  102. modules = modules.map do |m|
  103. m.is_a?(Symbol) ? ActionController.const_get(m) : m
  104. end
  105. MODULES - modules
  106. end
  107. 1 MODULES = [
  108. AbstractController::Rendering,
  109. UrlFor,
  110. Redirecting,
  111. ApiRendering,
  112. Renderers::All,
  113. ConditionalGet,
  114. BasicImplicitRender,
  115. StrongParameters,
  116. ForceSSL,
  117. DataStreaming,
  118. # Before callbacks should also be executed as early as possible, so
  119. # also include them at the bottom.
  120. AbstractController::Callbacks,
  121. # Append rescue at the bottom to wrap as much as possible.
  122. Rescue,
  123. # Add instrumentations hooks at the bottom, to ensure they instrument
  124. # all the methods properly.
  125. Instrumentation,
  126. # Params wrapper should come before instrumentation so they are
  127. # properly showed in logs
  128. ParamsWrapper
  129. ]
  130. 1 MODULES.each do |mod|
  131. 14 include mod
  132. end
  133. 1 ActiveSupport.run_load_hooks(:action_controller_api, self)
  134. 1 ActiveSupport.run_load_hooks(:action_controller, self)
  135. end
  136. end

target/rubygems/gems/actionpack-5.2.3/lib/action_controller/api/api_rendering.rb

75.0% lines covered

8 relevant lines. 6 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActionController
  3. 1 module ApiRendering
  4. 1 extend ActiveSupport::Concern
  5. 1 included do
  6. 1 include Rendering
  7. end
  8. 1 def render_to_body(options = {})
  9. _process_options(options)
  10. super
  11. end
  12. end
  13. end

target/rubygems/gems/actionpack-5.2.3/lib/action_controller/metal/testing.rb

62.5% lines covered

8 relevant lines. 5 lines covered and 3 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActionController
  3. 1 module Testing
  4. 1 extend ActiveSupport::Concern
  5. # Behavior specific to functional tests
  6. 1 module Functional # :nodoc:
  7. 1 def recycle!
  8. @_url_options = nil
  9. self.formats = nil
  10. self.params = nil
  11. end
  12. end
  13. end
  14. end

target/rubygems/gems/actionpack-5.2.3/lib/action_controller/template_assertions.rb

75.0% lines covered

4 relevant lines. 3 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActionController
  3. 1 module TemplateAssertions
  4. 1 def assert_template(options = {}, message = nil)
  5. raise NoMethodError,
  6. "assert_template has been extracted to a gem. To continue using it,
  7. add `gem 'rails-controller-testing'` to your Gemfile."
  8. end
  9. end
  10. end

target/rubygems/gems/actionpack-5.2.3/lib/action_controller/test_case.rb

33.2% lines covered

250 relevant lines. 83 lines covered and 167 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "rack/session/abstract/id"
  3. 1 require "active_support/core_ext/hash/conversions"
  4. 1 require "active_support/core_ext/object/to_query"
  5. 1 require "active_support/core_ext/module/anonymous"
  6. 1 require "active_support/core_ext/module/redefine_method"
  7. 1 require "active_support/core_ext/hash/keys"
  8. 1 require "active_support/testing/constant_lookup"
  9. 1 require "action_controller/template_assertions"
  10. 1 require "rails-dom-testing"
  11. 1 module ActionController
  12. 1 class Metal
  13. 1 include Testing::Functional
  14. end
  15. 1 module Live
  16. # Disable controller / rendering threads in tests. User tests can access
  17. # the database on the main thread, so they could open a txn, then the
  18. # controller thread will open a new connection and try to access data
  19. # that's only visible to the main thread's txn. This is the problem in #23483.
  20. 1 silence_redefinition_of_method :new_controller_thread
  21. 1 def new_controller_thread # :nodoc:
  22. yield
  23. end
  24. end
  25. # ActionController::TestCase will be deprecated and moved to a gem in Rails 5.1.
  26. # Please use ActionDispatch::IntegrationTest going forward.
  27. 1 class TestRequest < ActionDispatch::TestRequest #:nodoc:
  28. 1 DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup
  29. 1 DEFAULT_ENV.delete "PATH_INFO"
  30. 1 def self.new_session
  31. TestSession.new
  32. end
  33. 1 attr_reader :controller_class
  34. # Create a new test request with default `env` values.
  35. 1 def self.create(controller_class)
  36. env = {}
  37. env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
  38. env["rack.request.cookie_hash"] = {}.with_indifferent_access
  39. new(default_env.merge(env), new_session, controller_class)
  40. end
  41. 1 def self.default_env
  42. DEFAULT_ENV
  43. end
  44. 1 private_class_method :default_env
  45. 1 def initialize(env, session, controller_class)
  46. super(env)
  47. self.session = session
  48. self.session_options = TestSession::DEFAULT_OPTIONS.dup
  49. @controller_class = controller_class
  50. @custom_param_parsers = {
  51. xml: lambda { |raw_post| Hash.from_xml(raw_post)["hash"] }
  52. }
  53. end
  54. 1 def query_string=(string)
  55. set_header Rack::QUERY_STRING, string
  56. end
  57. 1 def content_type=(type)
  58. set_header "CONTENT_TYPE", type
  59. end
  60. 1 def assign_parameters(routes, controller_path, action, parameters, generated_path, query_string_keys)
  61. non_path_parameters = {}
  62. path_parameters = {}
  63. parameters.each do |key, value|
  64. if query_string_keys.include?(key)
  65. non_path_parameters[key] = value
  66. else
  67. if value.is_a?(Array)
  68. value = value.map(&:to_param)
  69. else
  70. value = value.to_param
  71. end
  72. path_parameters[key] = value
  73. end
  74. end
  75. if get?
  76. if query_string.blank?
  77. self.query_string = non_path_parameters.to_query
  78. end
  79. else
  80. if ENCODER.should_multipart?(non_path_parameters)
  81. self.content_type = ENCODER.content_type
  82. data = ENCODER.build_multipart non_path_parameters
  83. else
  84. fetch_header("CONTENT_TYPE") do |k|
  85. set_header k, "application/x-www-form-urlencoded"
  86. end
  87. case content_mime_type.to_sym
  88. when nil
  89. raise "Unknown Content-Type: #{content_type}"
  90. when :json
  91. data = ActiveSupport::JSON.encode(non_path_parameters)
  92. when :xml
  93. data = non_path_parameters.to_xml
  94. when :url_encoded_form
  95. data = non_path_parameters.to_query
  96. else
  97. @custom_param_parsers[content_mime_type.symbol] = ->(_) { non_path_parameters }
  98. data = non_path_parameters.to_query
  99. end
  100. end
  101. data_stream = StringIO.new(data)
  102. set_header "CONTENT_LENGTH", data_stream.length.to_s
  103. set_header "rack.input", data_stream
  104. end
  105. fetch_header("PATH_INFO") do |k|
  106. set_header k, generated_path
  107. end
  108. path_parameters[:controller] = controller_path
  109. path_parameters[:action] = action
  110. self.path_parameters = path_parameters
  111. end
  112. 1 ENCODER = Class.new do
  113. 1 include Rack::Test::Utils
  114. 1 def should_multipart?(params)
  115. # FIXME: lifted from Rack-Test. We should push this separation upstream.
  116. multipart = false
  117. query = lambda { |value|
  118. case value
  119. when Array
  120. value.each(&query)
  121. when Hash
  122. value.values.each(&query)
  123. when Rack::Test::UploadedFile
  124. multipart = true
  125. end
  126. }
  127. params.values.each(&query)
  128. multipart
  129. end
  130. 1 public :build_multipart
  131. 1 def content_type
  132. "multipart/form-data; boundary=#{Rack::Test::MULTIPART_BOUNDARY}"
  133. end
  134. end.new
  135. 1 private
  136. 1 def params_parsers
  137. super.merge @custom_param_parsers
  138. end
  139. end
  140. 1 class LiveTestResponse < Live::Response
  141. # Was the response successful?
  142. 1 alias_method :success?, :successful?
  143. # Was the URL not found?
  144. 1 alias_method :missing?, :not_found?
  145. # Was there a server-side error?
  146. 1 alias_method :error?, :server_error?
  147. end
  148. # Methods #destroy and #load! are overridden to avoid calling methods on the
  149. # @store object, which does not exist for the TestSession class.
  150. 1 class TestSession < Rack::Session::Abstract::SessionHash #:nodoc:
  151. 1 DEFAULT_OPTIONS = Rack::Session::Abstract::Persisted::DEFAULT_OPTIONS
  152. 1 def initialize(session = {})
  153. super(nil, nil)
  154. @id = SecureRandom.hex(16)
  155. @data = stringify_keys(session)
  156. @loaded = true
  157. end
  158. 1 def exists?
  159. true
  160. end
  161. 1 def keys
  162. @data.keys
  163. end
  164. 1 def values
  165. @data.values
  166. end
  167. 1 def destroy
  168. clear
  169. end
  170. 1 def fetch(key, *args, &block)
  171. @data.fetch(key.to_s, *args, &block)
  172. end
  173. 1 private
  174. 1 def load!
  175. @id
  176. end
  177. end
  178. # Superclass for ActionController functional tests. Functional tests allow you to
  179. # test a single controller action per test method.
  180. #
  181. # == Use integration style controller tests over functional style controller tests.
  182. #
  183. # Rails discourages the use of functional tests in favor of integration tests
  184. # (use ActionDispatch::IntegrationTest).
  185. #
  186. # New Rails applications no longer generate functional style controller tests and they should
  187. # only be used for backward compatibility. Integration style controller tests perform actual
  188. # requests, whereas functional style controller tests merely simulate a request. Besides,
  189. # integration tests are as fast as functional tests and provide lot of helpers such as +as+,
  190. # +parsed_body+ for effective testing of controller actions including even API endpoints.
  191. #
  192. # == Basic example
  193. #
  194. # Functional tests are written as follows:
  195. # 1. First, one uses the +get+, +post+, +patch+, +put+, +delete+ or +head+ method to simulate
  196. # an HTTP request.
  197. # 2. Then, one asserts whether the current state is as expected. "State" can be anything:
  198. # the controller's HTTP response, the database contents, etc.
  199. #
  200. # For example:
  201. #
  202. # class BooksControllerTest < ActionController::TestCase
  203. # def test_create
  204. # # Simulate a POST response with the given HTTP parameters.
  205. # post(:create, params: { book: { title: "Love Hina" }})
  206. #
  207. # # Asserts that the controller tried to redirect us to
  208. # # the created book's URI.
  209. # assert_response :found
  210. #
  211. # # Asserts that the controller really put the book in the database.
  212. # assert_not_nil Book.find_by(title: "Love Hina")
  213. # end
  214. # end
  215. #
  216. # You can also send a real document in the simulated HTTP request.
  217. #
  218. # def test_create
  219. # json = {book: { title: "Love Hina" }}.to_json
  220. # post :create, body: json
  221. # end
  222. #
  223. # == Special instance variables
  224. #
  225. # ActionController::TestCase will also automatically provide the following instance
  226. # variables for use in the tests:
  227. #
  228. # <b>@controller</b>::
  229. # The controller instance that will be tested.
  230. # <b>@request</b>::
  231. # An ActionController::TestRequest, representing the current HTTP
  232. # request. You can modify this object before sending the HTTP request. For example,
  233. # you might want to set some session properties before sending a GET request.
  234. # <b>@response</b>::
  235. # An ActionDispatch::TestResponse object, representing the response
  236. # of the last HTTP response. In the above example, <tt>@response</tt> becomes valid
  237. # after calling +post+. If the various assert methods are not sufficient, then you
  238. # may use this object to inspect the HTTP response in detail.
  239. #
  240. # (Earlier versions of \Rails required each functional test to subclass
  241. # Test::Unit::TestCase and define @controller, @request, @response in +setup+.)
  242. #
  243. # == Controller is automatically inferred
  244. #
  245. # ActionController::TestCase will automatically infer the controller under test
  246. # from the test class name. If the controller cannot be inferred from the test
  247. # class name, you can explicitly set it with +tests+.
  248. #
  249. # class SpecialEdgeCaseWidgetsControllerTest < ActionController::TestCase
  250. # tests WidgetController
  251. # end
  252. #
  253. # == \Testing controller internals
  254. #
  255. # In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions
  256. # can be used against. These collections are:
  257. #
  258. # * session: Objects being saved in the session.
  259. # * flash: The flash objects currently in the session.
  260. # * cookies: \Cookies being sent to the user on this request.
  261. #
  262. # These collections can be used just like any other hash:
  263. #
  264. # assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
  265. # assert flash.empty? # makes sure that there's nothing in the flash
  266. #
  267. # On top of the collections, you have the complete URL that a given action redirected to available in <tt>redirect_to_url</tt>.
  268. #
  269. # For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
  270. # action call which can then be asserted against.
  271. #
  272. # == Manipulating session and cookie variables
  273. #
  274. # Sometimes you need to set up the session and cookie variables for a test.
  275. # To do this just assign a value to the session or cookie collection:
  276. #
  277. # session[:key] = "value"
  278. # cookies[:key] = "value"
  279. #
  280. # To clear the cookies for a test just clear the cookie collection:
  281. #
  282. # cookies.clear
  283. #
  284. # == \Testing named routes
  285. #
  286. # If you're using named routes, they can be easily tested using the original named routes' methods straight in the test case.
  287. #
  288. # assert_redirected_to page_url(title: 'foo')
  289. 1 class TestCase < ActiveSupport::TestCase
  290. 1 module Behavior
  291. 1 extend ActiveSupport::Concern
  292. 1 include ActionDispatch::TestProcess
  293. 1 include ActiveSupport::Testing::ConstantLookup
  294. 1 include Rails::Dom::Testing::Assertions
  295. 1 attr_reader :response, :request
  296. 1 module ClassMethods
  297. # Sets the controller class name. Useful if the name can't be inferred from test class.
  298. # Normalizes +controller_class+ before using.
  299. #
  300. # tests WidgetController
  301. # tests :widget
  302. # tests 'widget'
  303. 1 def tests(controller_class)
  304. case controller_class
  305. when String, Symbol
  306. self.controller_class = "#{controller_class.to_s.camelize}Controller".constantize
  307. when Class
  308. self.controller_class = controller_class
  309. else
  310. raise ArgumentError, "controller class must be a String, Symbol, or Class"
  311. end
  312. end
  313. 1 def controller_class=(new_class)
  314. self._controller_class = new_class
  315. end
  316. 1 def controller_class
  317. if current_controller_class = _controller_class
  318. current_controller_class
  319. else
  320. self.controller_class = determine_default_controller_class(name)
  321. end
  322. end
  323. 1 def determine_default_controller_class(name)
  324. determine_constant_from_test_name(name) do |constant|
  325. Class === constant && constant < ActionController::Metal
  326. end
  327. end
  328. end
  329. # Simulate a GET request with the given parameters.
  330. #
  331. # - +action+: The controller action to call.
  332. # - +params+: The hash with HTTP parameters that you want to pass. This may be +nil+.
  333. # - +body+: The request body with a string that is appropriately encoded
  334. # (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
  335. # - +session+: A hash of parameters to store in the session. This may be +nil+.
  336. # - +flash+: A hash of parameters to store in the flash. This may be +nil+.
  337. #
  338. # You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with
  339. # +post+, +patch+, +put+, +delete+, and +head+.
  340. # Example sending parameters, session and setting a flash message:
  341. #
  342. # get :show,
  343. # params: { id: 7 },
  344. # session: { user_id: 1 },
  345. # flash: { notice: 'This is flash message' }
  346. #
  347. # Note that the request method is not verified. The different methods are
  348. # available to make the tests more expressive.
  349. 1 def get(action, **args)
  350. res = process(action, method: "GET", **args)
  351. cookies.update res.cookies
  352. res
  353. end
  354. # Simulate a POST request with the given parameters and set/volley the response.
  355. # See +get+ for more details.
  356. 1 def post(action, **args)
  357. process(action, method: "POST", **args)
  358. end
  359. # Simulate a PATCH request with the given parameters and set/volley the response.
  360. # See +get+ for more details.
  361. 1 def patch(action, **args)
  362. process(action, method: "PATCH", **args)
  363. end
  364. # Simulate a PUT request with the given parameters and set/volley the response.
  365. # See +get+ for more details.
  366. 1 def put(action, **args)
  367. process(action, method: "PUT", **args)
  368. end
  369. # Simulate a DELETE request with the given parameters and set/volley the response.
  370. # See +get+ for more details.
  371. 1 def delete(action, **args)
  372. process(action, method: "DELETE", **args)
  373. end
  374. # Simulate a HEAD request with the given parameters and set/volley the response.
  375. # See +get+ for more details.
  376. 1 def head(action, **args)
  377. process(action, method: "HEAD", **args)
  378. end
  379. # Simulate an HTTP request to +action+ by specifying request method,
  380. # parameters and set/volley the response.
  381. #
  382. # - +action+: The controller action to call.
  383. # - +method+: Request method used to send the HTTP request. Possible values
  384. # are +GET+, +POST+, +PATCH+, +PUT+, +DELETE+, +HEAD+. Defaults to +GET+. Can be a symbol.
  385. # - +params+: The hash with HTTP parameters that you want to pass. This may be +nil+.
  386. # - +body+: The request body with a string that is appropriately encoded
  387. # (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
  388. # - +session+: A hash of parameters to store in the session. This may be +nil+.
  389. # - +flash+: A hash of parameters to store in the flash. This may be +nil+.
  390. # - +format+: Request format. Defaults to +nil+. Can be string or symbol.
  391. # - +as+: Content type. Defaults to +nil+. Must be a symbol that corresponds
  392. # to a mime type.
  393. #
  394. # Example calling +create+ action and sending two params:
  395. #
  396. # process :create,
  397. # method: 'POST',
  398. # params: {
  399. # user: { name: 'Gaurish Sharma', email: 'user@example.com' }
  400. # },
  401. # session: { user_id: 1 },
  402. # flash: { notice: 'This is flash message' }
  403. #
  404. # To simulate +GET+, +POST+, +PATCH+, +PUT+, +DELETE+ and +HEAD+ requests
  405. # prefer using #get, #post, #patch, #put, #delete and #head methods
  406. # respectively which will make tests more expressive.
  407. #
  408. # Note that the request method is not verified.
  409. 1 def process(action, method: "GET", params: nil, session: nil, body: nil, flash: {}, format: nil, xhr: false, as: nil)
  410. check_required_ivars
  411. http_method = method.to_s.upcase
  412. @html_document = nil
  413. cookies.update(@request.cookies)
  414. cookies.update_cookies_from_jar
  415. @request.set_header "HTTP_COOKIE", cookies.to_header
  416. @request.delete_header "action_dispatch.cookies"
  417. @request = TestRequest.new scrub_env!(@request.env), @request.session, @controller.class
  418. @response = build_response @response_klass
  419. @response.request = @request
  420. @controller.recycle!
  421. if body
  422. @request.set_header "RAW_POST_DATA", body
  423. end
  424. @request.set_header "REQUEST_METHOD", http_method
  425. if as
  426. @request.content_type = Mime[as].to_s
  427. format ||= as
  428. end
  429. parameters = (params || {}).symbolize_keys
  430. if format
  431. parameters[:format] = format
  432. end
  433. generated_extras = @routes.generate_extras(parameters.merge(controller: controller_class_name, action: action.to_s))
  434. generated_path = generated_path(generated_extras)
  435. query_string_keys = query_parameter_names(generated_extras)
  436. @request.assign_parameters(@routes, controller_class_name, action.to_s, parameters, generated_path, query_string_keys)
  437. @request.session.update(session) if session
  438. @request.flash.update(flash || {})
  439. if xhr
  440. @request.set_header "HTTP_X_REQUESTED_WITH", "XMLHttpRequest"
  441. @request.fetch_header("HTTP_ACCEPT") do |k|
  442. @request.set_header k, [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(", ")
  443. end
  444. end
  445. @request.fetch_header("SCRIPT_NAME") do |k|
  446. @request.set_header k, @controller.config.relative_url_root
  447. end
  448. begin
  449. @controller.recycle!
  450. @controller.dispatch(action, @request, @response)
  451. ensure
  452. @request = @controller.request
  453. @response = @controller.response
  454. if @request.have_cookie_jar?
  455. unless @request.cookie_jar.committed?
  456. @request.cookie_jar.write(@response)
  457. cookies.update(@request.cookie_jar.instance_variable_get(:@cookies))
  458. end
  459. end
  460. @response.prepare!
  461. if flash_value = @request.flash.to_session_value
  462. @request.session["flash"] = flash_value
  463. else
  464. @request.session.delete("flash")
  465. end
  466. if xhr
  467. @request.delete_header "HTTP_X_REQUESTED_WITH"
  468. @request.delete_header "HTTP_ACCEPT"
  469. end
  470. @request.query_string = ""
  471. @response.sent!
  472. end
  473. @response
  474. end
  475. 1 def controller_class_name
  476. @controller.class.anonymous? ? "anonymous" : @controller.class.controller_path
  477. end
  478. 1 def generated_path(generated_extras)
  479. generated_extras[0]
  480. end
  481. 1 def query_parameter_names(generated_extras)
  482. generated_extras[1] + [:controller, :action]
  483. end
  484. 1 def setup_controller_request_and_response
  485. @controller = nil unless defined? @controller
  486. @response_klass = ActionDispatch::TestResponse
  487. if klass = self.class.controller_class
  488. if klass < ActionController::Live
  489. @response_klass = LiveTestResponse
  490. end
  491. unless @controller
  492. begin
  493. @controller = klass.new
  494. rescue
  495. warn "could not construct controller #{klass}" if $VERBOSE
  496. end
  497. end
  498. end
  499. @request = TestRequest.create(@controller.class)
  500. @response = build_response @response_klass
  501. @response.request = @request
  502. if @controller
  503. @controller.request = @request
  504. @controller.params = {}
  505. end
  506. end
  507. 1 def build_response(klass)
  508. klass.create
  509. end
  510. 1 included do
  511. 1 include ActionController::TemplateAssertions
  512. 1 include ActionDispatch::Assertions
  513. 1 class_attribute :_controller_class
  514. 1 setup :setup_controller_request_and_response
  515. 1 ActiveSupport.run_load_hooks(:action_controller_test_case, self)
  516. end
  517. 1 private
  518. 1 def scrub_env!(env)
  519. env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ }
  520. env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
  521. env.delete "action_dispatch.request.query_parameters"
  522. env.delete "action_dispatch.request.request_parameters"
  523. env["rack.input"] = StringIO.new
  524. env.delete "CONTENT_LENGTH"
  525. env.delete "RAW_POST_DATA"
  526. env
  527. end
  528. 1 def document_root_element
  529. html_document.root
  530. end
  531. 1 def check_required_ivars
  532. # Sanity check for required instance variables so we can give an
  533. # understandable error message.
  534. [:@routes, :@controller, :@request, :@response].each do |iv_name|
  535. if !instance_variable_defined?(iv_name) || instance_variable_get(iv_name).nil?
  536. raise "#{iv_name} is nil: make sure you set it in your test's setup method."
  537. end
  538. end
  539. end
  540. end
  541. 1 include Behavior
  542. end
  543. end

target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/callbacks.rb

88.89% lines covered

18 relevant lines. 16 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActionDispatch
  3. # Provides callbacks to be executed before and after dispatching the request.
  4. 1 class Callbacks
  5. 1 include ActiveSupport::Callbacks
  6. 1 define_callbacks :call
  7. 1 class << self
  8. 1 def before(*args, &block)
  9. set_callback(:call, :before, *args, &block)
  10. end
  11. 1 def after(*args, &block)
  12. set_callback(:call, :after, *args, &block)
  13. end
  14. end
  15. 1 def initialize(app)
  16. 1 @app = app
  17. end
  18. 1 def call(env)
  19. 2 error = nil
  20. 2 result = run_callbacks :call do
  21. 2 begin
  22. 2 @app.call(env)
  23. rescue => error
  24. end
  25. end
  26. 2 raise error if error
  27. 2 result
  28. end
  29. end
  30. end

target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/debug_exceptions.rb

33.33% lines covered

105 relevant lines. 35 lines covered and 70 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "action_dispatch/http/request"
  3. 1 require "action_dispatch/middleware/exception_wrapper"
  4. 1 require "action_dispatch/routing/inspector"
  5. 1 require "action_view"
  6. 1 require "action_view/base"
  7. 1 require "pp"
  8. 1 module ActionDispatch
  9. # This middleware is responsible for logging exceptions and
  10. # showing a debugging page in case the request is local.
  11. 1 class DebugExceptions
  12. 1 RESCUES_TEMPLATE_PATH = File.expand_path("templates", __dir__)
  13. 1 class DebugView < ActionView::Base
  14. 1 def debug_params(params)
  15. clean_params = params.clone
  16. clean_params.delete("action")
  17. clean_params.delete("controller")
  18. if clean_params.empty?
  19. "None"
  20. else
  21. PP.pp(clean_params, "".dup, 200)
  22. end
  23. end
  24. 1 def debug_headers(headers)
  25. if headers.present?
  26. headers.inspect.gsub(",", ",\n")
  27. else
  28. "None"
  29. end
  30. end
  31. 1 def debug_hash(object)
  32. object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
  33. end
  34. 1 def render(*)
  35. logger = ActionView::Base.logger
  36. if logger && logger.respond_to?(:silence)
  37. logger.silence { super }
  38. else
  39. super
  40. end
  41. end
  42. end
  43. 1 def initialize(app, routes_app = nil, response_format = :default)
  44. 1 @app = app
  45. 1 @routes_app = routes_app
  46. 1 @response_format = response_format
  47. end
  48. 1 def call(env)
  49. 2 request = ActionDispatch::Request.new env
  50. 2 _, headers, body = response = @app.call(env)
  51. 2 if headers["X-Cascade"] == "pass"
  52. body.close if body.respond_to?(:close)
  53. raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
  54. end
  55. 2 response
  56. rescue Exception => exception
  57. raise exception unless request.show_exceptions?
  58. render_exception(request, exception)
  59. end
  60. 1 private
  61. 1 def render_exception(request, exception)
  62. backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
  63. wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
  64. log_error(request, wrapper)
  65. if request.get_header("action_dispatch.show_detailed_exceptions")
  66. content_type = request.formats.first
  67. if api_request?(content_type)
  68. render_for_api_request(content_type, wrapper)
  69. else
  70. render_for_browser_request(request, wrapper)
  71. end
  72. else
  73. raise exception
  74. end
  75. end
  76. 1 def render_for_browser_request(request, wrapper)
  77. template = create_template(request, wrapper)
  78. file = "rescues/#{wrapper.rescue_template}"
  79. if request.xhr?
  80. body = template.render(template: file, layout: false, formats: [:text])
  81. format = "text/plain"
  82. else
  83. body = template.render(template: file, layout: "rescues/layout")
  84. format = "text/html"
  85. end
  86. render(wrapper.status_code, body, format)
  87. end
  88. 1 def render_for_api_request(content_type, wrapper)
  89. body = {
  90. status: wrapper.status_code,
  91. error: Rack::Utils::HTTP_STATUS_CODES.fetch(
  92. wrapper.status_code,
  93. Rack::Utils::HTTP_STATUS_CODES[500]
  94. ),
  95. exception: wrapper.exception.inspect,
  96. traces: wrapper.traces
  97. }
  98. to_format = "to_#{content_type.to_sym}"
  99. if content_type && body.respond_to?(to_format)
  100. formatted_body = body.public_send(to_format)
  101. format = content_type
  102. else
  103. formatted_body = body.to_json
  104. format = Mime[:json]
  105. end
  106. render(wrapper.status_code, formatted_body, format)
  107. end
  108. 1 def create_template(request, wrapper)
  109. traces = wrapper.traces
  110. trace_to_show = "Application Trace"
  111. if traces[trace_to_show].empty? && wrapper.rescue_template != "routing_error"
  112. trace_to_show = "Full Trace"
  113. end
  114. if source_to_show = traces[trace_to_show].first
  115. source_to_show_id = source_to_show[:id]
  116. end
  117. DebugView.new([RESCUES_TEMPLATE_PATH],
  118. request: request,
  119. exception: wrapper.exception,
  120. traces: traces,
  121. show_source_idx: source_to_show_id,
  122. trace_to_show: trace_to_show,
  123. routes_inspector: routes_inspector(wrapper.exception),
  124. source_extracts: wrapper.source_extracts,
  125. line_number: wrapper.line_number,
  126. file: wrapper.file
  127. )
  128. end
  129. 1 def render(status, body, format)
  130. [status, { "Content-Type" => "#{format}; charset=#{Response.default_charset}", "Content-Length" => body.bytesize.to_s }, [body]]
  131. end
  132. 1 def log_error(request, wrapper)
  133. logger = logger(request)
  134. return unless logger
  135. exception = wrapper.exception
  136. trace = wrapper.application_trace
  137. trace = wrapper.framework_trace if trace.empty?
  138. ActiveSupport::Deprecation.silence do
  139. logger.fatal " "
  140. logger.fatal "#{exception.class} (#{exception.message}):"
  141. log_array logger, exception.annoted_source_code if exception.respond_to?(:annoted_source_code)
  142. logger.fatal " "
  143. log_array logger, trace
  144. end
  145. end
  146. 1 def log_array(logger, array)
  147. if logger.formatter && logger.formatter.respond_to?(:tags_text)
  148. logger.fatal array.join("\n#{logger.formatter.tags_text}")
  149. else
  150. logger.fatal array.join("\n")
  151. end
  152. end
  153. 1 def logger(request)
  154. request.logger || ActionView::Base.logger || stderr_logger
  155. end
  156. 1 def stderr_logger
  157. @stderr_logger ||= ActiveSupport::Logger.new($stderr)
  158. end
  159. 1 def routes_inspector(exception)
  160. if @routes_app.respond_to?(:routes) && (exception.is_a?(ActionController::RoutingError) || exception.is_a?(ActionView::Template::Error))
  161. ActionDispatch::Routing::RoutesInspector.new(@routes_app.routes.routes)
  162. end
  163. end
  164. 1 def api_request?(content_type)
  165. @response_format == :api && !content_type.html?
  166. end
  167. end
  168. end

target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/exception_wrapper.rb

37.1% lines covered

62 relevant lines. 23 lines covered and 39 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/module/attribute_accessors"
  3. 1 require "rack/utils"
  4. 1 module ActionDispatch
  5. 1 class ExceptionWrapper
  6. 1 cattr_accessor :rescue_responses, default: Hash.new(:internal_server_error).merge!(
  7. "ActionController::RoutingError" => :not_found,
  8. "AbstractController::ActionNotFound" => :not_found,
  9. "ActionController::MethodNotAllowed" => :method_not_allowed,
  10. "ActionController::UnknownHttpMethod" => :method_not_allowed,
  11. "ActionController::NotImplemented" => :not_implemented,
  12. "ActionController::UnknownFormat" => :not_acceptable,
  13. "ActionController::InvalidAuthenticityToken" => :unprocessable_entity,
  14. "ActionController::InvalidCrossOriginRequest" => :unprocessable_entity,
  15. "ActionDispatch::Http::Parameters::ParseError" => :bad_request,
  16. "ActionController::BadRequest" => :bad_request,
  17. "ActionController::ParameterMissing" => :bad_request,
  18. "Rack::QueryParser::ParameterTypeError" => :bad_request,
  19. "Rack::QueryParser::InvalidParameterError" => :bad_request
  20. )
  21. 1 cattr_accessor :rescue_templates, default: Hash.new("diagnostics").merge!(
  22. "ActionView::MissingTemplate" => "missing_template",
  23. "ActionController::RoutingError" => "routing_error",
  24. "AbstractController::ActionNotFound" => "unknown_action",
  25. "ActiveRecord::StatementInvalid" => "invalid_statement",
  26. "ActionView::Template::Error" => "template_error"
  27. )
  28. 1 attr_reader :backtrace_cleaner, :exception, :line_number, :file
  29. 1 def initialize(backtrace_cleaner, exception)
  30. @backtrace_cleaner = backtrace_cleaner
  31. @exception = original_exception(exception)
  32. expand_backtrace if exception.is_a?(SyntaxError) || exception.cause.is_a?(SyntaxError)
  33. end
  34. 1 def rescue_template
  35. @@rescue_templates[@exception.class.name]
  36. end
  37. 1 def status_code
  38. self.class.status_code_for_exception(@exception.class.name)
  39. end
  40. 1 def application_trace
  41. clean_backtrace(:silent)
  42. end
  43. 1 def framework_trace
  44. clean_backtrace(:noise)
  45. end
  46. 1 def full_trace
  47. clean_backtrace(:all)
  48. end
  49. 1 def traces
  50. application_trace_with_ids = []
  51. framework_trace_with_ids = []
  52. full_trace_with_ids = []
  53. full_trace.each_with_index do |trace, idx|
  54. trace_with_id = { id: idx, trace: trace }
  55. if application_trace.include?(trace)
  56. application_trace_with_ids << trace_with_id
  57. else
  58. framework_trace_with_ids << trace_with_id
  59. end
  60. full_trace_with_ids << trace_with_id
  61. end
  62. {
  63. "Application Trace" => application_trace_with_ids,
  64. "Framework Trace" => framework_trace_with_ids,
  65. "Full Trace" => full_trace_with_ids
  66. }
  67. end
  68. 1 def self.status_code_for_exception(class_name)
  69. Rack::Utils.status_code(@@rescue_responses[class_name])
  70. end
  71. 1 def source_extracts
  72. backtrace.map do |trace|
  73. file, line_number = extract_file_and_line_number(trace)
  74. {
  75. code: source_fragment(file, line_number),
  76. line_number: line_number
  77. }
  78. end
  79. end
  80. 1 private
  81. 1 def backtrace
  82. Array(@exception.backtrace)
  83. end
  84. 1 def original_exception(exception)
  85. if @@rescue_responses.has_key?(exception.cause.class.name)
  86. exception.cause
  87. else
  88. exception
  89. end
  90. end
  91. 1 def clean_backtrace(*args)
  92. if backtrace_cleaner
  93. backtrace_cleaner.clean(backtrace, *args)
  94. else
  95. backtrace
  96. end
  97. end
  98. 1 def source_fragment(path, line)
  99. return unless Rails.respond_to?(:root) && Rails.root
  100. full_path = Rails.root.join(path)
  101. if File.exist?(full_path)
  102. File.open(full_path, "r") do |file|
  103. start = [line - 3, 0].max
  104. lines = file.each_line.drop(start).take(6)
  105. Hash[*(start + 1..(lines.count + start)).zip(lines).flatten]
  106. end
  107. end
  108. end
  109. 1 def extract_file_and_line_number(trace)
  110. # Split by the first colon followed by some digits, which works for both
  111. # Windows and Unix path styles.
  112. file, line = trace.match(/^(.+?):(\d+).*$/, &:captures) || trace
  113. [file, line.to_i]
  114. end
  115. 1 def expand_backtrace
  116. @exception.backtrace.unshift(
  117. @exception.to_s.split("\n")
  118. ).flatten!
  119. end
  120. end
  121. end

target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/executor.rb

100.0% lines covered

11 relevant lines. 11 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "rack/body_proxy"
  3. 1 module ActionDispatch
  4. 1 class Executor
  5. 1 def initialize(app, executor)
  6. 2 @app, @executor = app, executor
  7. end
  8. 1 def call(env)
  9. 4 state = @executor.run!
  10. 4 begin
  11. 4 response = @app.call(env)
  12. 8 returned = response << ::Rack::BodyProxy.new(response.pop) { state.complete! }
  13. ensure
  14. 4 state.complete! unless returned
  15. end
  16. end
  17. end
  18. end

target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/flash.rb

52.31% lines covered

130 relevant lines. 68 lines covered and 62 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/hash/keys"
  3. 1 module ActionDispatch
  4. # The flash provides a way to pass temporary primitive-types (String, Array, Hash) between actions. Anything you place in the flash will be exposed
  5. # to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create
  6. # action that sets <tt>flash[:notice] = "Post successfully created"</tt> before redirecting to a display action that can
  7. # then expose the flash to its template. Actually, that exposure is automatically done.
  8. #
  9. # class PostsController < ActionController::Base
  10. # def create
  11. # # save post
  12. # flash[:notice] = "Post successfully created"
  13. # redirect_to @post
  14. # end
  15. #
  16. # def show
  17. # # doesn't need to assign the flash notice to the template, that's done automatically
  18. # end
  19. # end
  20. #
  21. # show.html.erb
  22. # <% if flash[:notice] %>
  23. # <div class="notice"><%= flash[:notice] %></div>
  24. # <% end %>
  25. #
  26. # Since the +notice+ and +alert+ keys are a common idiom, convenience accessors are available:
  27. #
  28. # flash.alert = "You must be logged in"
  29. # flash.notice = "Post successfully created"
  30. #
  31. # This example places a string in the flash. And of course, you can put as many as you like at a time too. If you want to pass
  32. # non-primitive types, you will have to handle that in your application. Example: To show messages with links, you will have to
  33. # use sanitize helper.
  34. #
  35. # Just remember: They'll be gone by the time the next action has been performed.
  36. #
  37. # See docs on the FlashHash class for more details about the flash.
  38. 1 class Flash
  39. 1 KEY = "action_dispatch.request.flash_hash".freeze
  40. 1 module RequestMethods
  41. # Access the contents of the flash. Use <tt>flash["notice"]</tt> to
  42. # read a notice you put there or <tt>flash["notice"] = "hello"</tt>
  43. # to put a new one.
  44. 1 def flash
  45. 1 flash = flash_hash
  46. 1 return flash if flash
  47. 1 self.flash = Flash::FlashHash.from_session_value(session["flash"])
  48. end
  49. 1 def flash=(flash)
  50. 1 set_header Flash::KEY, flash
  51. end
  52. 1 def flash_hash # :nodoc:
  53. 3 get_header Flash::KEY
  54. end
  55. 1 def commit_flash # :nodoc:
  56. 2 session = self.session || {}
  57. 2 flash_hash = self.flash_hash
  58. 2 if flash_hash && (flash_hash.present? || session.key?("flash"))
  59. session["flash"] = flash_hash.to_session_value
  60. self.flash = flash_hash.dup
  61. end
  62. 2 if (!session.respond_to?(:loaded?) || session.loaded?) && # reset_session uses {}, which doesn't implement #loaded?
  63. 1 session.key?("flash") && session["flash"].nil?
  64. session.delete("flash")
  65. end
  66. end
  67. 1 def reset_session # :nodoc:
  68. super
  69. self.flash = nil
  70. end
  71. end
  72. 1 class FlashNow #:nodoc:
  73. 1 attr_accessor :flash
  74. 1 def initialize(flash)
  75. @flash = flash
  76. end
  77. 1 def []=(k, v)
  78. k = k.to_s
  79. @flash[k] = v
  80. @flash.discard(k)
  81. v
  82. end
  83. 1 def [](k)
  84. @flash[k.to_s]
  85. end
  86. # Convenience accessor for <tt>flash.now[:alert]=</tt>.
  87. 1 def alert=(message)
  88. self[:alert] = message
  89. end
  90. # Convenience accessor for <tt>flash.now[:notice]=</tt>.
  91. 1 def notice=(message)
  92. self[:notice] = message
  93. end
  94. end
  95. 1 class FlashHash
  96. 1 include Enumerable
  97. 1 def self.from_session_value(value) #:nodoc:
  98. 1 case value
  99. when FlashHash # Rails 3.1, 3.2
  100. flashes = value.instance_variable_get(:@flashes)
  101. if discard = value.instance_variable_get(:@used)
  102. flashes.except!(*discard)
  103. end
  104. new(flashes, flashes.keys)
  105. when Hash # Rails 4.0
  106. flashes = value["flashes"]
  107. if discard = value["discard"]
  108. flashes.except!(*discard)
  109. end
  110. new(flashes, flashes.keys)
  111. else
  112. 1 new
  113. end
  114. end
  115. # Builds a hash containing the flashes to keep for the next request.
  116. # If there are none to keep, returns +nil+.
  117. 1 def to_session_value #:nodoc:
  118. flashes_to_keep = @flashes.except(*@discard)
  119. return nil if flashes_to_keep.empty?
  120. { "discard" => [], "flashes" => flashes_to_keep }
  121. end
  122. 1 def initialize(flashes = {}, discard = []) #:nodoc:
  123. 1 @discard = Set.new(stringify_array(discard))
  124. 1 @flashes = flashes.stringify_keys
  125. 1 @now = nil
  126. end
  127. 1 def initialize_copy(other)
  128. if other.now_is_loaded?
  129. @now = other.now.dup
  130. @now.flash = self
  131. end
  132. super
  133. end
  134. 1 def []=(k, v)
  135. k = k.to_s
  136. @discard.delete k
  137. @flashes[k] = v
  138. end
  139. 1 def [](k)
  140. @flashes[k.to_s]
  141. end
  142. 1 def update(h) #:nodoc:
  143. @discard.subtract stringify_array(h.keys)
  144. @flashes.update h.stringify_keys
  145. self
  146. end
  147. 1 def keys
  148. @flashes.keys
  149. end
  150. 1 def key?(name)
  151. @flashes.key? name.to_s
  152. end
  153. 1 def delete(key)
  154. key = key.to_s
  155. @discard.delete key
  156. @flashes.delete key
  157. self
  158. end
  159. 1 def to_hash
  160. @flashes.dup
  161. end
  162. 1 def empty?
  163. 1 @flashes.empty?
  164. end
  165. 1 def clear
  166. @discard.clear
  167. @flashes.clear
  168. end
  169. 1 def each(&block)
  170. 1 @flashes.each(&block)
  171. end
  172. 1 alias :merge! :update
  173. 1 def replace(h) #:nodoc:
  174. @discard.clear
  175. @flashes.replace h.stringify_keys
  176. self
  177. end
  178. # Sets a flash that will not be available to the next action, only to the current.
  179. #
  180. # flash.now[:message] = "Hello current action"
  181. #
  182. # This method enables you to use the flash as a central messaging system in your app.
  183. # When you need to pass an object to the next action, you use the standard flash assign (<tt>[]=</tt>).
  184. # When you need to pass an object to the current action, you use <tt>now</tt>, and your object will
  185. # vanish when the current action is done.
  186. #
  187. # Entries set via <tt>now</tt> are accessed the same way as standard entries: <tt>flash['my-key']</tt>.
  188. #
  189. # Also, brings two convenience accessors:
  190. #
  191. # flash.now.alert = "Beware now!"
  192. # # Equivalent to flash.now[:alert] = "Beware now!"
  193. #
  194. # flash.now.notice = "Good luck now!"
  195. # # Equivalent to flash.now[:notice] = "Good luck now!"
  196. 1 def now
  197. @now ||= FlashNow.new(self)
  198. end
  199. # Keeps either the entire current flash or a specific flash entry available for the next action:
  200. #
  201. # flash.keep # keeps the entire flash
  202. # flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded
  203. 1 def keep(k = nil)
  204. k = k.to_s if k
  205. @discard.subtract Array(k || keys)
  206. k ? self[k] : self
  207. end
  208. # Marks the entire flash or a single flash entry to be discarded by the end of the current action:
  209. #
  210. # flash.discard # discard the entire flash at the end of the current action
  211. # flash.discard(:warning) # discard only the "warning" entry at the end of the current action
  212. 1 def discard(k = nil)
  213. k = k.to_s if k
  214. @discard.merge Array(k || keys)
  215. k ? self[k] : self
  216. end
  217. # Mark for removal entries that were kept, and delete unkept ones.
  218. #
  219. # This method is called automatically by filters, so you generally don't need to care about it.
  220. 1 def sweep #:nodoc:
  221. @discard.each { |k| @flashes.delete k }
  222. @discard.replace @flashes.keys
  223. end
  224. # Convenience accessor for <tt>flash[:alert]</tt>.
  225. 1 def alert
  226. self[:alert]
  227. end
  228. # Convenience accessor for <tt>flash[:alert]=</tt>.
  229. 1 def alert=(message)
  230. self[:alert] = message
  231. end
  232. # Convenience accessor for <tt>flash[:notice]</tt>.
  233. 1 def notice
  234. self[:notice]
  235. end
  236. # Convenience accessor for <tt>flash[:notice]=</tt>.
  237. 1 def notice=(message)
  238. self[:notice] = message
  239. end
  240. 1 protected
  241. 1 def now_is_loaded?
  242. @now
  243. end
  244. 1 private
  245. 1 def stringify_array(array) # :doc:
  246. 1 array.map do |item|
  247. item.kind_of?(Symbol) ? item.to_s : item
  248. end
  249. end
  250. end
  251. 2 def self.new(app) app; end
  252. end
  253. 1 class Request
  254. 1 prepend Flash::RequestMethods
  255. end
  256. end

target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/public_exceptions.rb

40.0% lines covered

25 relevant lines. 10 lines covered and 15 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActionDispatch
  3. # When called, this middleware renders an error page. By default if an HTML
  4. # response is expected it will render static error pages from the <tt>/public</tt>
  5. # directory. For example when this middleware receives a 500 response it will
  6. # render the template found in <tt>/public/500.html</tt>.
  7. # If an internationalized locale is set, this middleware will attempt to render
  8. # the template in <tt>/public/500.<locale>.html</tt>. If an internationalized template
  9. # is not found it will fall back on <tt>/public/500.html</tt>.
  10. #
  11. # When a request with a content type other than HTML is made, this middleware
  12. # will attempt to convert error information into the appropriate response type.
  13. 1 class PublicExceptions
  14. 1 attr_accessor :public_path
  15. 1 def initialize(public_path)
  16. 1 @public_path = public_path
  17. end
  18. 1 def call(env)
  19. request = ActionDispatch::Request.new(env)
  20. status = request.path_info[1..-1].to_i
  21. content_type = request.formats.first
  22. body = { status: status, error: Rack::Utils::HTTP_STATUS_CODES.fetch(status, Rack::Utils::HTTP_STATUS_CODES[500]) }
  23. render(status, content_type, body)
  24. end
  25. 1 private
  26. 1 def render(status, content_type, body)
  27. format = "to_#{content_type.to_sym}" if content_type
  28. if format && body.respond_to?(format)
  29. render_format(status, content_type, body.public_send(format))
  30. else
  31. render_html(status)
  32. end
  33. end
  34. 1 def render_format(status, content_type, body)
  35. [status, { "Content-Type" => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
  36. "Content-Length" => body.bytesize.to_s }, [body]]
  37. end
  38. 1 def render_html(status)
  39. path = "#{public_path}/#{status}.#{I18n.locale}.html"
  40. path = "#{public_path}/#{status}.html" unless (found = File.exist?(path))
  41. if found || File.exist?(path)
  42. render_format(status, "text/html", File.read(path))
  43. else
  44. [404, { "X-Cascade" => "pass" }, []]
  45. end
  46. end
  47. end
  48. end

target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/reloader.rb

100.0% lines covered

2 relevant lines. 2 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActionDispatch
  3. # ActionDispatch::Reloader wraps the request with callbacks provided by ActiveSupport::Reloader
  4. # callbacks, intended to assist with code reloading during development.
  5. #
  6. # By default, ActionDispatch::Reloader is included in the middleware stack
  7. # only in the development environment; specifically, when +config.cache_classes+
  8. # is false.
  9. 1 class Reloader < Executor
  10. end
  11. end

target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/remote_ip.rb

83.67% lines covered

49 relevant lines. 41 lines covered and 8 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "ipaddr"
  3. 1 module ActionDispatch
  4. # This middleware calculates the IP address of the remote client that is
  5. # making the request. It does this by checking various headers that could
  6. # contain the address, and then picking the last-set address that is not
  7. # on the list of trusted IPs. This follows the precedent set by e.g.
  8. # {the Tomcat server}[https://issues.apache.org/bugzilla/show_bug.cgi?id=50453],
  9. # with {reasoning explained at length}[http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection]
  10. # by @gingerlime. A more detailed explanation of the algorithm is given
  11. # at GetIp#calculate_ip.
  12. #
  13. # Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2]
  14. # requires. Some Rack servers simply drop preceding headers, and only report
  15. # the value that was {given in the last header}[http://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-servers].
  16. # If you are behind multiple proxy servers (like NGINX to HAProxy to Unicorn)
  17. # then you should test your Rack server to make sure your data is good.
  18. #
  19. # IF YOU DON'T USE A PROXY, THIS MAKES YOU VULNERABLE TO IP SPOOFING.
  20. # This middleware assumes that there is at least one proxy sitting around
  21. # and setting headers with the client's remote IP address. If you don't use
  22. # a proxy, because you are hosted on e.g. Heroku without SSL, any client can
  23. # claim to have any IP address by setting the X-Forwarded-For header. If you
  24. # care about that, then you need to explicitly drop or ignore those headers
  25. # sometime before this middleware runs.
  26. 1 class RemoteIp
  27. 1 class IpSpoofAttackError < StandardError; end
  28. # The default trusted IPs list simply includes IP addresses that are
  29. # guaranteed by the IP specification to be private addresses. Those will
  30. # not be the ultimate client IP in production, and so are discarded. See
  31. # https://en.wikipedia.org/wiki/Private_network for details.
  32. TRUSTED_PROXIES = [
  33. "127.0.0.1", # localhost IPv4
  34. "::1", # localhost IPv6
  35. "fc00::/7", # private IPv6 range fc00::/7
  36. "10.0.0.0/8", # private IPv4 range 10.x.x.x
  37. "172.16.0.0/12", # private IPv4 range 172.16.0.0 .. 172.31.255.255
  38. "192.168.0.0/16", # private IPv4 range 192.168.x.x
  39. 6 ].map { |proxy| IPAddr.new(proxy) }
  40. 1 attr_reader :check_ip, :proxies
  41. # Create a new +RemoteIp+ middleware instance.
  42. #
  43. # The +ip_spoofing_check+ option is on by default. When on, an exception
  44. # is raised if it looks like the client is trying to lie about its own IP
  45. # address. It makes sense to turn off this check on sites aimed at non-IP
  46. # clients (like WAP devices), or behind proxies that set headers in an
  47. # incorrect or confusing way (like AWS ELB).
  48. #
  49. # The +custom_proxies+ argument can take an Array of string, IPAddr, or
  50. # Regexp objects which will be used instead of +TRUSTED_PROXIES+. If a
  51. # single string, IPAddr, or Regexp object is provided, it will be used in
  52. # addition to +TRUSTED_PROXIES+. Any proxy setup will put the value you
  53. # want in the middle (or at the beginning) of the X-Forwarded-For list,
  54. # with your proxy servers after it. If your proxies aren't removed, pass
  55. # them in via the +custom_proxies+ parameter. That way, the middleware will
  56. # ignore those IP addresses, and return the one that you want.
  57. 1 def initialize(app, ip_spoofing_check = true, custom_proxies = nil)
  58. 1 @app = app
  59. 1 @check_ip = ip_spoofing_check
  60. 1 @proxies = if custom_proxies.blank?
  61. 1 TRUSTED_PROXIES
  62. elsif custom_proxies.respond_to?(:any?)
  63. custom_proxies
  64. else
  65. Array(custom_proxies) + TRUSTED_PROXIES
  66. end
  67. end
  68. # Since the IP address may not be needed, we store the object here
  69. # without calculating the IP to keep from slowing down the majority of
  70. # requests. For those requests that do need to know the IP, the
  71. # GetIp#calculate_ip method will calculate the memoized client IP address.
  72. 1 def call(env)
  73. 2 req = ActionDispatch::Request.new env
  74. 2 req.remote_ip = GetIp.new(req, check_ip, proxies)
  75. 2 @app.call(req.env)
  76. end
  77. # The GetIp class exists as a way to defer processing of the request data
  78. # into an actual IP address. If the ActionDispatch::Request#remote_ip method
  79. # is called, this class will calculate the value and then memoize it.
  80. 1 class GetIp
  81. 1 def initialize(req, check_ip, proxies)
  82. 2 @req = req
  83. 2 @check_ip = check_ip
  84. 2 @proxies = proxies
  85. end
  86. # Sort through the various IP address headers, looking for the IP most
  87. # likely to be the address of the actual remote client making this
  88. # request.
  89. #
  90. # REMOTE_ADDR will be correct if the request is made directly against the
  91. # Ruby process, on e.g. Heroku. When the request is proxied by another
  92. # server like HAProxy or NGINX, the IP address that made the original
  93. # request will be put in an X-Forwarded-For header. If there are multiple
  94. # proxies, that header may contain a list of IPs. Other proxy services
  95. # set the Client-Ip header instead, so we check that too.
  96. #
  97. # As discussed in {this post about Rails IP Spoofing}[http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/],
  98. # while the first IP in the list is likely to be the "originating" IP,
  99. # it could also have been set by the client maliciously.
  100. #
  101. # In order to find the first address that is (probably) accurate, we
  102. # take the list of IPs, remove known and trusted proxies, and then take
  103. # the last address left, which was presumably set by one of those proxies.
  104. 1 def calculate_ip
  105. # Set by the Rack web server, this is a single value.
  106. 2 remote_addr = ips_from(@req.remote_addr).last
  107. # Could be a CSV list and/or repeated headers that were concatenated.
  108. 2 client_ips = ips_from(@req.client_ip).reverse
  109. 2 forwarded_ips = ips_from(@req.x_forwarded_for).reverse
  110. # +Client-Ip+ and +X-Forwarded-For+ should not, generally, both be set.
  111. # If they are both set, it means that either:
  112. #
  113. # 1) This request passed through two proxies with incompatible IP header
  114. # conventions.
  115. # 2) The client passed one of +Client-Ip+ or +X-Forwarded-For+
  116. # (whichever the proxy servers weren't using) themselves.
  117. #
  118. # Either way, there is no way for us to determine which header is the
  119. # right one after the fact. Since we have no idea, if we are concerned
  120. # about IP spoofing we need to give up and explode. (If you're not
  121. # concerned about IP spoofing you can turn the +ip_spoofing_check+
  122. # option off.)
  123. 2 should_check_ip = @check_ip && client_ips.last && forwarded_ips.last
  124. 2 if should_check_ip && !forwarded_ips.include?(client_ips.last)
  125. # We don't know which came from the proxy, and which from the user
  126. raise IpSpoofAttackError, "IP spoofing attack?! " \
  127. "HTTP_CLIENT_IP=#{@req.client_ip.inspect} " \
  128. "HTTP_X_FORWARDED_FOR=#{@req.x_forwarded_for.inspect}"
  129. end
  130. # We assume these things about the IP headers:
  131. #
  132. # - X-Forwarded-For will be a list of IPs, one per proxy, or blank
  133. # - Client-Ip is propagated from the outermost proxy, or is blank
  134. # - REMOTE_ADDR will be the IP that made the request to Rack
  135. 2 ips = [forwarded_ips, client_ips, remote_addr].flatten.compact
  136. # If every single IP option is in the trusted list, just return REMOTE_ADDR
  137. 2 filter_proxies(ips).first || remote_addr
  138. end
  139. # Memoizes the value returned by #calculate_ip and returns it for
  140. # ActionDispatch::Request to use.
  141. 1 def to_s
  142. 3 @ip ||= calculate_ip
  143. end
  144. 1 private
  145. 1 def ips_from(header) # :doc:
  146. 6 return [] unless header
  147. # Split the comma-separated list into an array of strings.
  148. 2 ips = header.strip.split(/[,\s]+/)
  149. 2 ips.select do |ip|
  150. 2 begin
  151. # Only return IPs that are valid according to the IPAddr#new method.
  152. 2 range = IPAddr.new(ip).to_range
  153. # We want to make sure nobody is sneaking a netmask in.
  154. 2 range.begin == range.end
  155. rescue ArgumentError
  156. nil
  157. end
  158. end
  159. end
  160. 1 def filter_proxies(ips) # :doc:
  161. 2 ips.reject do |ip|
  162. 4 @proxies.any? { |proxy| proxy === ip }
  163. end
  164. end
  165. end
  166. end
  167. end

target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/request_id.rb

94.44% lines covered

18 relevant lines. 17 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "securerandom"
  3. 1 require "active_support/core_ext/string/access"
  4. 1 module ActionDispatch
  5. # Makes a unique request id available to the +action_dispatch.request_id+ env variable (which is then accessible
  6. # through <tt>ActionDispatch::Request#request_id</tt> or the alias <tt>ActionDispatch::Request#uuid</tt>) and sends
  7. # the same id to the client via the X-Request-Id header.
  8. #
  9. # The unique request id is either based on the X-Request-Id header in the request, which would typically be generated
  10. # by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the
  11. # header is accepted from the outside world, we sanitize it to a max of 255 chars and alphanumeric and dashes only.
  12. #
  13. # The unique request id can be used to trace a request end-to-end and would typically end up being part of log files
  14. # from multiple pieces of the stack.
  15. 1 class RequestId
  16. 1 X_REQUEST_ID = "X-Request-Id".freeze #:nodoc:
  17. 1 def initialize(app)
  18. 1 @app = app
  19. end
  20. 1 def call(env)
  21. 2 req = ActionDispatch::Request.new env
  22. 2 req.request_id = make_request_id(req.x_request_id)
  23. 4 @app.call(env).tap { |_status, headers, _body| headers[X_REQUEST_ID] = req.request_id }
  24. end
  25. 1 private
  26. 1 def make_request_id(request_id)
  27. 2 if request_id.presence
  28. request_id.gsub(/[^\w\-@]/, "".freeze).first(255)
  29. else
  30. 2 internal_request_id
  31. end
  32. end
  33. 1 def internal_request_id
  34. 2 SecureRandom.uuid
  35. end
  36. end
  37. end

target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/session/abstract_store.rb

76.47% lines covered

51 relevant lines. 39 lines covered and 12 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "rack/utils"
  3. 1 require "rack/request"
  4. 1 require "rack/session/abstract/id"
  5. 1 require "action_dispatch/middleware/cookies"
  6. 1 require "action_dispatch/request/session"
  7. 1 module ActionDispatch
  8. 1 module Session
  9. 1 class SessionRestoreError < StandardError #:nodoc:
  10. 1 def initialize
  11. super("Session contains objects whose class definition isn't available.\n" \
  12. "Remember to require the classes for all objects kept in the session.\n" \
  13. "(Original exception: #{$!.message} [#{$!.class}])\n")
  14. set_backtrace $!.backtrace
  15. end
  16. end
  17. 1 module Compatibility
  18. 1 def initialize(app, options = {})
  19. 1 options[:key] ||= "_session_id"
  20. 1 super
  21. end
  22. 1 def generate_sid
  23. 4 sid = SecureRandom.hex(16)
  24. 4 sid.encode!(Encoding::UTF_8)
  25. 4 sid
  26. end
  27. 1 private
  28. 1 def initialize_sid # :doc:
  29. 1 @default_options.delete(:sidbits)
  30. 1 @default_options.delete(:secure_random)
  31. end
  32. 1 def make_request(env)
  33. 2 ActionDispatch::Request.new env
  34. end
  35. end
  36. 1 module StaleSessionCheck
  37. 1 def load_session(env)
  38. stale_session_check! { super }
  39. end
  40. 1 def extract_session_id(env)
  41. stale_session_check! { super }
  42. end
  43. 1 def stale_session_check!
  44. 4 yield
  45. rescue ArgumentError => argument_error
  46. if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
  47. begin
  48. # Note that the regexp does not allow $1 to end with a ':'.
  49. $1.constantize
  50. rescue LoadError, NameError
  51. raise ActionDispatch::Session::SessionRestoreError
  52. end
  53. retry
  54. else
  55. raise
  56. end
  57. end
  58. end
  59. 1 module SessionObject # :nodoc:
  60. 1 def prepare_session(req)
  61. 2 Request::Session.create(self, req, @default_options)
  62. end
  63. 1 def loaded_session?(session)
  64. 4 !session.is_a?(Request::Session) || session.loaded?
  65. end
  66. end
  67. 1 class AbstractStore < Rack::Session::Abstract::Persisted
  68. 1 include Compatibility
  69. 1 include StaleSessionCheck
  70. 1 include SessionObject
  71. 1 private
  72. 1 def set_cookie(request, session_id, cookie)
  73. request.cookie_jar[key] = cookie
  74. end
  75. end
  76. end
  77. end

target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/session/cookie_store.rb

92.68% lines covered

41 relevant lines. 38 lines covered and 3 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/hash/keys"
  3. 1 require "action_dispatch/middleware/session/abstract_store"
  4. 1 require "rack/session/cookie"
  5. 1 module ActionDispatch
  6. 1 module Session
  7. # This cookie-based session store is the Rails default. It is
  8. # dramatically faster than the alternatives.
  9. #
  10. # Sessions typically contain at most a user_id and flash message; both fit
  11. # within the 4K cookie size limit. A CookieOverflow exception is raised if
  12. # you attempt to store more than 4K of data.
  13. #
  14. # The cookie jar used for storage is automatically configured to be the
  15. # best possible option given your application's configuration.
  16. #
  17. # If you only have secret_token set, your cookies will be signed, but
  18. # not encrypted. This means a user cannot alter their +user_id+ without
  19. # knowing your app's secret key, but can easily read their +user_id+. This
  20. # was the default for Rails 3 apps.
  21. #
  22. # Your cookies will be encrypted using your apps secret_key_base. This
  23. # goes a step further than signed cookies in that encrypted cookies cannot
  24. # be altered or read by users. This is the default starting in Rails 4.
  25. #
  26. # Configure your session store in <tt>config/initializers/session_store.rb</tt>:
  27. #
  28. # Rails.application.config.session_store :cookie_store, key: '_your_app_session'
  29. #
  30. # In the development and test environments your application's secret key base is
  31. # generated by Rails and stored in a temporary file in <tt>tmp/development_secret.txt</tt>.
  32. # In all other environments, it is stored encrypted in the
  33. # <tt>config/credentials.yml.enc</tt> file.
  34. #
  35. # If your application was not updated to Rails 5.2 defaults, the secret_key_base
  36. # will be found in the old <tt>config/secrets.yml</tt> file.
  37. #
  38. # Note that changing your secret_key_base will invalidate all existing session.
  39. # Additionally, you should take care to make sure you are not relying on the
  40. # ability to decode signed cookies generated by your app in external
  41. # applications or JavaScript before changing it.
  42. #
  43. # Because CookieStore extends Rack::Session::Abstract::Persisted, many of the
  44. # options described there can be used to customize the session cookie that
  45. # is generated. For example:
  46. #
  47. # Rails.application.config.session_store :cookie_store, expire_after: 14.days
  48. #
  49. # would set the session cookie to expire automatically 14 days after creation.
  50. # Other useful options include <tt>:key</tt>, <tt>:secure</tt> and
  51. # <tt>:httponly</tt>.
  52. 1 class CookieStore < AbstractStore
  53. 1 def initialize(app, options = {})
  54. 1 super(app, options.merge!(cookie_only: true))
  55. end
  56. 1 def delete_session(req, session_id, options)
  57. 2 new_sid = generate_sid unless options[:drop]
  58. # Reset hash and Assign the new session id
  59. 2 req.set_header("action_dispatch.request.unsigned_session_cookie", new_sid ? { "session_id" => new_sid } : {})
  60. 2 new_sid
  61. end
  62. 1 def load_session(req)
  63. 2 stale_session_check! do
  64. 2 data = unpacked_cookie_data(req)
  65. 2 data = persistent_session_id!(data)
  66. 2 [data["session_id"], data]
  67. end
  68. end
  69. 1 private
  70. 1 def extract_session_id(req)
  71. stale_session_check! do
  72. unpacked_cookie_data(req)["session_id"]
  73. end
  74. end
  75. 1 def unpacked_cookie_data(req)
  76. 2 req.fetch_header("action_dispatch.request.unsigned_session_cookie") do |k|
  77. 2 v = stale_session_check! do
  78. 2 if data = get_cookie(req)
  79. data.stringify_keys!
  80. end
  81. 2 data || {}
  82. end
  83. 2 req.set_header k, v
  84. end
  85. end
  86. 1 def persistent_session_id!(data, sid = nil)
  87. 2 data ||= {}
  88. 2 data["session_id"] ||= sid || generate_sid
  89. 2 data
  90. end
  91. 1 def write_session(req, sid, session_data, options)
  92. 2 session_data["session_id"] = sid
  93. 2 session_data
  94. end
  95. 1 def set_cookie(request, session_id, cookie)
  96. 2 cookie_jar(request)[@key] = cookie
  97. end
  98. 1 def get_cookie(req)
  99. 2 cookie_jar(req)[@key]
  100. end
  101. 1 def cookie_jar(request)
  102. 4 request.cookie_jar.signed_or_encrypted
  103. end
  104. end
  105. end
  106. end

target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/show_exceptions.rb

50.0% lines covered

28 relevant lines. 14 lines covered and 14 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "action_dispatch/http/request"
  3. 1 require "action_dispatch/middleware/exception_wrapper"
  4. 1 module ActionDispatch
  5. # This middleware rescues any exception returned by the application
  6. # and calls an exceptions app that will wrap it in a format for the end user.
  7. #
  8. # The exceptions app should be passed as parameter on initialization
  9. # of ShowExceptions. Every time there is an exception, ShowExceptions will
  10. # store the exception in env["action_dispatch.exception"], rewrite the
  11. # PATH_INFO to the exception status code and call the Rack app.
  12. #
  13. # If the application returns a "X-Cascade" pass response, this middleware
  14. # will send an empty response as result with the correct status code.
  15. # If any exception happens inside the exceptions app, this middleware
  16. # catches the exceptions and returns a FAILSAFE_RESPONSE.
  17. 1 class ShowExceptions
  18. 1 FAILSAFE_RESPONSE = [500, { "Content-Type" => "text/plain" },
  19. ["500 Internal Server Error\n" \
  20. "If you are the administrator of this website, then please read this web " \
  21. "application's log file and/or the web server's log file to find out what " \
  22. "went wrong."]]
  23. 1 def initialize(app, exceptions_app)
  24. 1 @app = app
  25. 1 @exceptions_app = exceptions_app
  26. end
  27. 1 def call(env)
  28. 2 request = ActionDispatch::Request.new env
  29. 2 @app.call(env)
  30. rescue Exception => exception
  31. if request.show_exceptions?
  32. render_exception(request, exception)
  33. else
  34. raise exception
  35. end
  36. end
  37. 1 private
  38. 1 def render_exception(request, exception)
  39. backtrace_cleaner = request.get_header "action_dispatch.backtrace_cleaner"
  40. wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
  41. status = wrapper.status_code
  42. request.set_header "action_dispatch.exception", wrapper.exception
  43. request.set_header "action_dispatch.original_path", request.path_info
  44. request.path_info = "/#{status}"
  45. response = @exceptions_app.call(request.env)
  46. response[1]["X-Cascade"] == "pass" ? pass_response(status) : response
  47. rescue Exception => failsafe_error
  48. $stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
  49. FAILSAFE_RESPONSE
  50. end
  51. 1 def pass_response(status)
  52. [status, { "Content-Type" => "text/html; charset=#{Response.default_charset}", "Content-Length" => "0" }, []]
  53. end
  54. end
  55. end

target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/middleware/static.rb

58.33% lines covered

60 relevant lines. 35 lines covered and 25 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "rack/utils"
  3. 1 require "active_support/core_ext/uri"
  4. 1 module ActionDispatch
  5. # This middleware returns a file's contents from disk in the body response.
  6. # When initialized, it can accept optional HTTP headers, which will be set
  7. # when a response containing a file's contents is delivered.
  8. #
  9. # This middleware will render the file specified in <tt>env["PATH_INFO"]</tt>
  10. # where the base path is in the +root+ directory. For example, if the +root+
  11. # is set to +public/+, then a request with <tt>env["PATH_INFO"]</tt> of
  12. # +assets/application.js+ will return a response with the contents of a file
  13. # located at +public/assets/application.js+ if the file exists. If the file
  14. # does not exist, a 404 "File not Found" response will be returned.
  15. 1 class FileHandler
  16. 1 def initialize(root, index: "index", headers: {})
  17. 1 @root = root.chomp("/").b
  18. 1 @file_server = ::Rack::File.new(@root, headers)
  19. 1 @index = index
  20. end
  21. # Takes a path to a file. If the file is found, has valid encoding, and has
  22. # correct read permissions, the return value is a URI-escaped string
  23. # representing the filename. Otherwise, false is returned.
  24. #
  25. # Used by the +Static+ class to check the existence of a valid file
  26. # in the server's +public/+ directory (see Static#call).
  27. 1 def match?(path)
  28. 2 path = ::Rack::Utils.unescape_path path
  29. 2 return false unless ::Rack::Utils.valid_path? path
  30. 2 path = ::Rack::Utils.clean_path_info path
  31. 2 paths = [path, "#{path}#{ext}", "#{path}/#{@index}#{ext}"]
  32. 2 if match = paths.detect { |p|
  33. 6 path = File.join(@root, p.b)
  34. 6 begin
  35. 6 File.file?(path) && File.readable?(path)
  36. rescue SystemCallError
  37. false
  38. end
  39. }
  40. return ::Rack::Utils.escape_path(match).b
  41. end
  42. end
  43. 1 def call(env)
  44. serve(Rack::Request.new(env))
  45. end
  46. 1 def serve(request)
  47. path = request.path_info
  48. gzip_path = gzip_file_path(path)
  49. if gzip_path && gzip_encoding_accepted?(request)
  50. request.path_info = gzip_path
  51. status, headers, body = @file_server.call(request.env)
  52. if status == 304
  53. return [status, headers, body]
  54. end
  55. headers["Content-Encoding"] = "gzip"
  56. headers["Content-Type"] = content_type(path)
  57. else
  58. status, headers, body = @file_server.call(request.env)
  59. end
  60. headers["Vary"] = "Accept-Encoding" if gzip_path
  61. return [status, headers, body]
  62. ensure
  63. request.path_info = path
  64. end
  65. 1 private
  66. 1 def ext
  67. 4 ::ActionController::Base.default_static_extension
  68. end
  69. 1 def content_type(path)
  70. ::Rack::Mime.mime_type(::File.extname(path), "text/plain".freeze)
  71. end
  72. 1 def gzip_encoding_accepted?(request)
  73. request.accept_encoding.any? { |enc, quality| enc =~ /\bgzip\b/i }
  74. end
  75. 1 def gzip_file_path(path)
  76. can_gzip_mime = content_type(path) =~ /\A(?:text\/|application\/javascript)/
  77. gzip_path = "#{path}.gz"
  78. if can_gzip_mime && File.exist?(File.join(@root, ::Rack::Utils.unescape_path(gzip_path).b))
  79. gzip_path.b
  80. else
  81. false
  82. end
  83. end
  84. end
  85. # This middleware will attempt to return the contents of a file's body from
  86. # disk in the response. If a file is not found on disk, the request will be
  87. # delegated to the application stack. This middleware is commonly initialized
  88. # to serve assets from a server's +public/+ directory.
  89. #
  90. # This middleware verifies the path to ensure that only files
  91. # living in the root directory can be rendered. A request cannot
  92. # produce a directory traversal using this middleware. Only 'GET' and 'HEAD'
  93. # requests will result in a file being returned.
  94. 1 class Static
  95. 1 def initialize(app, path, index: "index", headers: {})
  96. 1 @app = app
  97. 1 @file_handler = FileHandler.new(path, index: index, headers: headers)
  98. end
  99. 1 def call(env)
  100. 2 req = Rack::Request.new env
  101. 2 if req.get? || req.head?
  102. 2 path = req.path_info.chomp("/".freeze)
  103. 2 if match = @file_handler.match?(path)
  104. req.path_info = match
  105. return @file_handler.serve(req)
  106. end
  107. end
  108. 2 @app.call(req.env)
  109. end
  110. end
  111. end

target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/request/session.rb

75.65% lines covered

115 relevant lines. 87 lines covered and 28 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "rack/session/abstract/id"
  3. 1 module ActionDispatch
  4. 1 class Request
  5. # Session is responsible for lazily loading the session from store.
  6. 1 class Session # :nodoc:
  7. 1 ENV_SESSION_KEY = Rack::RACK_SESSION # :nodoc:
  8. 1 ENV_SESSION_OPTIONS_KEY = Rack::RACK_SESSION_OPTIONS # :nodoc:
  9. # Singleton object used to determine if an optional param wasn't specified.
  10. 1 Unspecified = Object.new
  11. # Creates a session hash, merging the properties of the previous session if any.
  12. 1 def self.create(store, req, default_options)
  13. 2 session_was = find req
  14. 2 session = Request::Session.new(store, req)
  15. 2 session.merge! session_was if session_was
  16. 2 set(req, session)
  17. 2 Options.set(req, Request::Session::Options.new(store, default_options))
  18. 2 session
  19. end
  20. 1 def self.find(req)
  21. 2 req.get_header ENV_SESSION_KEY
  22. end
  23. 1 def self.set(req, session)
  24. 2 req.set_header ENV_SESSION_KEY, session
  25. end
  26. 1 class Options #:nodoc:
  27. 1 def self.set(req, options)
  28. 2 req.set_header ENV_SESSION_OPTIONS_KEY, options
  29. end
  30. 1 def self.find(req)
  31. 6 req.get_header ENV_SESSION_OPTIONS_KEY
  32. end
  33. 1 def initialize(by, default_options)
  34. 2 @by = by
  35. 2 @delegate = default_options.dup
  36. end
  37. 1 def [](key)
  38. 16 @delegate[key]
  39. end
  40. 1 def id(req)
  41. 2 @delegate.fetch(:id) {
  42. @by.send(:extract_session_id, req)
  43. }
  44. end
  45. 5 def []=(k, v); @delegate[k] = v; end
  46. 3 def to_hash; @delegate.dup; end
  47. 1 def values_at(*args); @delegate.values_at(*args); end
  48. end
  49. 1 def initialize(by, req)
  50. 2 @by = by
  51. 2 @req = req
  52. 2 @delegate = {}
  53. 2 @loaded = false
  54. 2 @exists = nil # We haven't checked yet.
  55. end
  56. 1 def id
  57. 2 options.id(@req)
  58. end
  59. 1 def options
  60. 6 Options.find @req
  61. end
  62. 1 def destroy
  63. clear
  64. options = self.options || {}
  65. @by.send(:delete_session, @req, options.id(@req), options)
  66. # Load the new sid to be written with the response.
  67. @loaded = false
  68. load_for_write!
  69. end
  70. # Returns value of the key stored in the session or
  71. # +nil+ if the given key is not found in the session.
  72. 1 def [](key)
  73. 3 load_for_read!
  74. 3 @delegate[key.to_s]
  75. end
  76. # Returns true if the session has the given key or false.
  77. 1 def has_key?(key)
  78. 3 load_for_read!
  79. 3 @delegate.key?(key.to_s)
  80. end
  81. 1 alias :key? :has_key?
  82. 1 alias :include? :has_key?
  83. # Returns keys of the session as Array.
  84. 1 def keys
  85. load_for_read!
  86. @delegate.keys
  87. end
  88. # Returns values of the session as Array.
  89. 1 def values
  90. load_for_read!
  91. @delegate.values
  92. end
  93. # Writes given value to given key of the session.
  94. 1 def []=(key, value)
  95. 3 load_for_write!
  96. 3 @delegate[key.to_s] = value
  97. end
  98. # Clears the session.
  99. 1 def clear
  100. load_for_write!
  101. @delegate.clear
  102. end
  103. # Returns the session as Hash.
  104. 1 def to_hash
  105. 2 load_for_read!
  106. 7 @delegate.dup.delete_if { |_, v| v.nil? }
  107. end
  108. 1 alias :to_h :to_hash
  109. # Updates the session with given Hash.
  110. #
  111. # session.to_hash
  112. # # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2"}
  113. #
  114. # session.update({ "foo" => "bar" })
  115. # # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2", "foo" => "bar"}
  116. #
  117. # session.to_hash
  118. # # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2", "foo" => "bar"}
  119. 1 def update(hash)
  120. load_for_write!
  121. @delegate.update stringify_keys(hash)
  122. end
  123. # Deletes given key from the session.
  124. 1 def delete(key)
  125. 2 load_for_write!
  126. 2 @delegate.delete key.to_s
  127. end
  128. # Returns value of the given key from the session, or raises +KeyError+
  129. # if can't find the given key and no default value is set.
  130. # Returns default value if specified.
  131. #
  132. # session.fetch(:foo)
  133. # # => KeyError: key not found: "foo"
  134. #
  135. # session.fetch(:foo, :bar)
  136. # # => :bar
  137. #
  138. # session.fetch(:foo) do
  139. # :bar
  140. # end
  141. # # => :bar
  142. 1 def fetch(key, default = Unspecified, &block)
  143. load_for_read!
  144. if default == Unspecified
  145. @delegate.fetch(key.to_s, &block)
  146. else
  147. @delegate.fetch(key.to_s, default, &block)
  148. end
  149. end
  150. 1 def inspect
  151. if loaded?
  152. super
  153. else
  154. "#<#{self.class}:0x#{(object_id << 1).to_s(16)} not yet loaded>"
  155. end
  156. end
  157. 1 def exists?
  158. return @exists unless @exists.nil?
  159. @exists = @by.send(:session_exists?, @req)
  160. end
  161. 1 def loaded?
  162. 19 @loaded
  163. end
  164. 1 def empty?
  165. load_for_read!
  166. @delegate.empty?
  167. end
  168. 1 def merge!(other)
  169. load_for_write!
  170. @delegate.merge!(other)
  171. end
  172. 1 def each(&block)
  173. to_hash.each(&block)
  174. end
  175. 1 private
  176. 1 def load_for_read!
  177. 8 load! if !loaded? && exists?
  178. end
  179. 1 def load_for_write!
  180. 5 load! unless loaded?
  181. end
  182. 1 def load!
  183. 2 id, session = @by.load_session @req
  184. 2 options[:id] = id
  185. 2 @delegate.replace(stringify_keys(session))
  186. 2 @loaded = true
  187. end
  188. 1 def stringify_keys(other)
  189. 2 other.each_with_object({}) { |(key, value), hash|
  190. 2 hash[key.to_s] = value
  191. }
  192. end
  193. end
  194. end
  195. end

target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/request/utils.rb

58.33% lines covered

36 relevant lines. 21 lines covered and 15 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/hash/indifferent_access"
  3. 1 module ActionDispatch
  4. 1 class Request
  5. 1 class Utils # :nodoc:
  6. 1 mattr_accessor :perform_deep_munge, default: true
  7. 1 def self.each_param_value(params, &block)
  8. case params
  9. when Array
  10. params.each { |element| each_param_value(element, &block) }
  11. when Hash
  12. params.each_value { |value| each_param_value(value, &block) }
  13. when String
  14. block.call params
  15. end
  16. end
  17. 1 def self.normalize_encode_params(params)
  18. 4 if perform_deep_munge
  19. 4 NoNilParamEncoder.normalize_encode_params params
  20. else
  21. ParamEncoder.normalize_encode_params params
  22. end
  23. end
  24. 1 def self.check_param_encoding(params)
  25. 8 case params
  26. when Array
  27. params.each { |element| check_param_encoding(element) }
  28. when Hash
  29. 8 params.each_value { |value| check_param_encoding(value) }
  30. when String
  31. 4 unless params.valid_encoding?
  32. # Raise Rack::Utils::InvalidParameterError for consistency with Rack.
  33. # ActionDispatch::Request#GET will re-raise as a BadRequest error.
  34. raise Rack::Utils::InvalidParameterError, "Invalid encoding for parameter: #{params.scrub}"
  35. end
  36. end
  37. end
  38. 1 class ParamEncoder # :nodoc:
  39. # Convert nested Hash to HashWithIndifferentAccess.
  40. 1 def self.normalize_encode_params(params)
  41. 4 case params
  42. when Array
  43. handle_array params
  44. when Hash
  45. 4 if params.has_key?(:tempfile)
  46. ActionDispatch::Http::UploadedFile.new(params)
  47. else
  48. 4 params.each_with_object({}) do |(key, val), new_hash|
  49. new_hash[key] = normalize_encode_params(val)
  50. end.with_indifferent_access
  51. end
  52. else
  53. params
  54. end
  55. end
  56. 1 def self.handle_array(params)
  57. params.map! { |el| normalize_encode_params(el) }
  58. end
  59. end
  60. # Remove nils from the params hash.
  61. 1 class NoNilParamEncoder < ParamEncoder # :nodoc:
  62. 1 def self.handle_array(params)
  63. list = super
  64. list.compact!
  65. list
  66. end
  67. end
  68. end
  69. end
  70. end

target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/routing/inspector.rb

37.27% lines covered

110 relevant lines. 41 lines covered and 69 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "delegate"
  3. 1 require "active_support/core_ext/string/strip"
  4. 1 module ActionDispatch
  5. 1 module Routing
  6. 1 class RouteWrapper < SimpleDelegator
  7. 1 def endpoint
  8. app.dispatcher? ? "#{controller}##{action}" : rack_app.inspect
  9. end
  10. 1 def constraints
  11. requirements.except(:controller, :action)
  12. end
  13. 1 def rack_app
  14. app.rack_app
  15. end
  16. 1 def path
  17. super.spec.to_s
  18. end
  19. 1 def name
  20. super.to_s
  21. end
  22. 1 def reqs
  23. @reqs ||= begin
  24. reqs = endpoint
  25. reqs += " #{constraints}" unless constraints.empty?
  26. reqs
  27. end
  28. end
  29. 1 def controller
  30. parts.include?(:controller) ? ":controller" : requirements[:controller]
  31. end
  32. 1 def action
  33. parts.include?(:action) ? ":action" : requirements[:action]
  34. end
  35. 1 def internal?
  36. internal
  37. end
  38. 1 def engine?
  39. app.engine?
  40. end
  41. end
  42. ##
  43. # This class is just used for displaying route information when someone
  44. # executes `rails routes` or looks at the RoutingError page.
  45. # People should not use this class.
  46. 1 class RoutesInspector # :nodoc:
  47. 1 def initialize(routes)
  48. @engines = {}
  49. @routes = routes
  50. end
  51. 1 def format(formatter, filter = nil)
  52. routes_to_display = filter_routes(normalize_filter(filter))
  53. routes = collect_routes(routes_to_display)
  54. if routes.none?
  55. formatter.no_routes(collect_routes(@routes))
  56. return formatter.result
  57. end
  58. formatter.header routes
  59. formatter.section routes
  60. @engines.each do |name, engine_routes|
  61. formatter.section_title "Routes for #{name}"
  62. formatter.section engine_routes
  63. end
  64. formatter.result
  65. end
  66. 1 private
  67. 1 def normalize_filter(filter)
  68. if filter.is_a?(Hash) && filter[:controller]
  69. { controller: /#{filter[:controller].underscore.sub(/_?controller\z/, "")}/ }
  70. elsif filter
  71. { controller: /#{filter}/, action: /#{filter}/, verb: /#{filter}/, name: /#{filter}/, path: /#{filter}/ }
  72. end
  73. end
  74. 1 def filter_routes(filter)
  75. if filter
  76. @routes.select do |route|
  77. route_wrapper = RouteWrapper.new(route)
  78. filter.any? { |default, value| route_wrapper.send(default) =~ value }
  79. end
  80. else
  81. @routes
  82. end
  83. end
  84. 1 def collect_routes(routes)
  85. routes.collect do |route|
  86. RouteWrapper.new(route)
  87. end.reject(&:internal?).collect do |route|
  88. collect_engine_routes(route)
  89. { name: route.name,
  90. verb: route.verb,
  91. path: route.path,
  92. reqs: route.reqs }
  93. end
  94. end
  95. 1 def collect_engine_routes(route)
  96. name = route.endpoint
  97. return unless route.engine?
  98. return if @engines[name]
  99. routes = route.rack_app.routes
  100. if routes.is_a?(ActionDispatch::Routing::RouteSet)
  101. @engines[name] = collect_routes(routes.routes)
  102. end
  103. end
  104. end
  105. 1 class ConsoleFormatter
  106. 1 def initialize
  107. @buffer = []
  108. end
  109. 1 def result
  110. @buffer.join("\n")
  111. end
  112. 1 def section_title(title)
  113. @buffer << "\n#{title}:"
  114. end
  115. 1 def section(routes)
  116. @buffer << draw_section(routes)
  117. end
  118. 1 def header(routes)
  119. @buffer << draw_header(routes)
  120. end
  121. 1 def no_routes(routes)
  122. @buffer <<
  123. if routes.none?
  124. <<-MESSAGE.strip_heredoc
  125. You don't have any routes defined!
  126. Please add some routes in config/routes.rb.
  127. MESSAGE
  128. else
  129. "No routes were found for this controller"
  130. end
  131. @buffer << "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html."
  132. end
  133. 1 private
  134. 1 def draw_section(routes)
  135. header_lengths = ["Prefix", "Verb", "URI Pattern"].map(&:length)
  136. name_width, verb_width, path_width = widths(routes).zip(header_lengths).map(&:max)
  137. routes.map do |r|
  138. "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}"
  139. end
  140. end
  141. 1 def draw_header(routes)
  142. name_width, verb_width, path_width = widths(routes)
  143. "#{"Prefix".rjust(name_width)} #{"Verb".ljust(verb_width)} #{"URI Pattern".ljust(path_width)} Controller#Action"
  144. end
  145. 1 def widths(routes)
  146. [routes.map { |r| r[:name].length }.max || 0,
  147. routes.map { |r| r[:verb].length }.max || 0,
  148. routes.map { |r| r[:path].length }.max || 0]
  149. end
  150. end
  151. 1 class HtmlTableFormatter
  152. 1 def initialize(view)
  153. @view = view
  154. @buffer = []
  155. end
  156. 1 def section_title(title)
  157. @buffer << %(<tr><th colspan="4">#{title}</th></tr>)
  158. end
  159. 1 def section(routes)
  160. @buffer << @view.render(partial: "routes/route", collection: routes)
  161. end
  162. # The header is part of the HTML page, so we don't construct it here.
  163. 1 def header(routes)
  164. end
  165. 1 def no_routes(*)
  166. @buffer << <<-MESSAGE.strip_heredoc
  167. <p>You don't have any routes defined!</p>
  168. <ul>
  169. <li>Please add some routes in <tt>config/routes.rb</tt>.</li>
  170. <li>
  171. For more information about routes, please see the Rails guide
  172. <a href="http://guides.rubyonrails.org/routing.html">Rails Routing from the Outside In</a>.
  173. </li>
  174. </ul>
  175. MESSAGE
  176. end
  177. 1 def result
  178. @view.raw @view.render(layout: "routes/table") {
  179. @view.raw @buffer.join("\n")
  180. }
  181. end
  182. end
  183. end
  184. end

target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/testing/assertion_response.rb

100.0% lines covered

19 relevant lines. 19 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActionDispatch
  3. # This is a class that abstracts away an asserted response. It purposely
  4. # does not inherit from Response because it doesn't need it. That means it
  5. # does not have headers or a body.
  6. 1 class AssertionResponse
  7. 1 attr_reader :code, :name
  8. 1 GENERIC_RESPONSE_CODES = { # :nodoc:
  9. success: "2XX",
  10. missing: "404",
  11. redirect: "3XX",
  12. error: "5XX"
  13. }
  14. # Accepts a specific response status code as an Integer (404) or String
  15. # ('404') or a response status range as a Symbol pseudo-code (:success,
  16. # indicating any 200-299 status code).
  17. 1 def initialize(code_or_name)
  18. 4 if code_or_name.is_a?(Symbol)
  19. 2 @name = code_or_name
  20. 2 @code = code_from_name(code_or_name)
  21. else
  22. 2 @name = name_from_code(code_or_name)
  23. 2 @code = code_or_name
  24. end
  25. 4 raise ArgumentError, "Invalid response name: #{name}" if @code.nil?
  26. 4 raise ArgumentError, "Invalid response code: #{code}" if @name.nil?
  27. end
  28. 1 def code_and_name
  29. 4 "#{code}: #{name}"
  30. end
  31. 1 private
  32. 1 def code_from_name(name)
  33. 2 GENERIC_RESPONSE_CODES[name] || Rack::Utils::SYMBOL_TO_STATUS_CODE[name]
  34. end
  35. 1 def name_from_code(code)
  36. 2 GENERIC_RESPONSE_CODES.invert[code] || Rack::Utils::HTTP_STATUS_CODES[code]
  37. end
  38. end
  39. end

target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/testing/assertions.rb

76.92% lines covered

13 relevant lines. 10 lines covered and 3 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "rails-dom-testing"
  3. 1 module ActionDispatch
  4. 1 module Assertions
  5. 1 autoload :ResponseAssertions, "action_dispatch/testing/assertions/response"
  6. 1 autoload :RoutingAssertions, "action_dispatch/testing/assertions/routing"
  7. 1 extend ActiveSupport::Concern
  8. 1 include ResponseAssertions
  9. 1 include RoutingAssertions
  10. 1 include Rails::Dom::Testing::Assertions
  11. 1 def html_document
  12. @html_document ||= if @response.content_type.to_s.end_with?("xml")
  13. Nokogiri::XML::Document.parse(@response.body)
  14. else
  15. Nokogiri::HTML::Document.parse(@response.body)
  16. end
  17. end
  18. end
  19. end

target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/testing/assertions/response.rb

89.74% lines covered

39 relevant lines. 35 lines covered and 4 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActionDispatch
  3. 1 module Assertions
  4. # A small suite of assertions that test responses from \Rails applications.
  5. 1 module ResponseAssertions
  6. 1 RESPONSE_PREDICATES = { # :nodoc:
  7. success: :successful?,
  8. missing: :not_found?,
  9. redirect: :redirection?,
  10. error: :server_error?,
  11. }
  12. # Asserts that the response is one of the following types:
  13. #
  14. # * <tt>:success</tt> - Status code was in the 200-299 range
  15. # * <tt>:redirect</tt> - Status code was in the 300-399 range
  16. # * <tt>:missing</tt> - Status code was 404
  17. # * <tt>:error</tt> - Status code was in the 500-599 range
  18. #
  19. # You can also pass an explicit status number like <tt>assert_response(501)</tt>
  20. # or its symbolic equivalent <tt>assert_response(:not_implemented)</tt>.
  21. # See Rack::Utils::SYMBOL_TO_STATUS_CODE for a full list.
  22. #
  23. # # Asserts that the response was a redirection
  24. # assert_response :redirect
  25. #
  26. # # Asserts that the response code was status code 401 (unauthorized)
  27. # assert_response 401
  28. 1 def assert_response(type, message = nil)
  29. 2 message ||= generate_response_message(type)
  30. 2 if RESPONSE_PREDICATES.keys.include?(type)
  31. 2 assert @response.send(RESPONSE_PREDICATES[type]), message
  32. else
  33. assert_equal AssertionResponse.new(type).code, @response.response_code, message
  34. end
  35. end
  36. # Asserts that the redirection options passed in match those of the redirect called in the latest action.
  37. # This match can be partial, such that <tt>assert_redirected_to(controller: "weblog")</tt> will also
  38. # match the redirection of <tt>redirect_to(controller: "weblog", action: "show")</tt> and so on.
  39. #
  40. # # Asserts that the redirection was to the "index" action on the WeblogController
  41. # assert_redirected_to controller: "weblog", action: "index"
  42. #
  43. # # Asserts that the redirection was to the named route login_url
  44. # assert_redirected_to login_url
  45. #
  46. # # Asserts that the redirection was to the URL for @customer
  47. # assert_redirected_to @customer
  48. #
  49. # # Asserts that the redirection matches the regular expression
  50. # assert_redirected_to %r(\Ahttp://example.org)
  51. 1 def assert_redirected_to(options = {}, message = nil)
  52. 1 assert_response(:redirect, message)
  53. 1 return true if options === @response.location
  54. 1 redirect_is = normalize_argument_to_redirection(@response.location)
  55. 1 redirect_expected = normalize_argument_to_redirection(options)
  56. 1 message ||= "Expected response to be a redirect to <#{redirect_expected}> but was a redirect to <#{redirect_is}>"
  57. 1 assert_operator redirect_expected, :===, redirect_is, message
  58. end
  59. 1 private
  60. # Proxy to to_param if the object will respond to it.
  61. 1 def parameterize(value)
  62. value.respond_to?(:to_param) ? value.to_param : value
  63. end
  64. 1 def normalize_argument_to_redirection(fragment)
  65. 3 if Regexp === fragment
  66. fragment
  67. else
  68. 3 handle = @controller || ActionController::Redirecting
  69. 3 handle._compute_redirect_to_location(@request, fragment)
  70. end
  71. end
  72. 3 def generate_response_message(expected, actual = @response.response_code)
  73. 8 "Expected response to be a <#{code_with_name(expected)}>,"\
  74. 2 " but was a <#{code_with_name(actual)}>"
  75. 3 .dup.concat(location_if_redirected).concat(response_body_if_short)
  76. end
  77. 1 def response_body_if_short
  78. 2 return "" if @response.body.size > 500
  79. 1 "\nResponse body: #{@response.body}"
  80. end
  81. 1 def location_if_redirected
  82. 2 return "" unless @response.redirection? && @response.location.present?
  83. 1 location = normalize_argument_to_redirection(@response.location)
  84. 1 " redirect to <#{location}>"
  85. end
  86. 1 def code_with_name(code_or_name)
  87. 4 if RESPONSE_PREDICATES.values.include?("#{code_or_name}?".to_sym)
  88. code_or_name = RESPONSE_PREDICATES.invert["#{code_or_name}?".to_sym]
  89. end
  90. 4 AssertionResponse.new(code_or_name).code_and_name
  91. end
  92. end
  93. end
  94. end

target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/testing/assertions/routing.rb

19.23% lines covered

78 relevant lines. 15 lines covered and 63 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "uri"
  3. 1 require "active_support/core_ext/hash/indifferent_access"
  4. 1 require "active_support/core_ext/string/access"
  5. 1 require "action_controller/metal/exceptions"
  6. 1 module ActionDispatch
  7. 1 module Assertions
  8. # Suite of assertions to test routes generated by \Rails and the handling of requests made to them.
  9. 1 module RoutingAssertions
  10. # Asserts that the routing of the given +path+ was handled correctly and that the parsed options (given in the +expected_options+ hash)
  11. # match +path+. Basically, it asserts that \Rails recognizes the route given by +expected_options+.
  12. #
  13. # Pass a hash in the second argument (+path+) to specify the request method. This is useful for routes
  14. # requiring a specific HTTP method. The hash should contain a :path with the incoming request path
  15. # and a :method containing the required HTTP verb.
  16. #
  17. # # Asserts that POSTing to /items will call the create action on ItemsController
  18. # assert_recognizes({controller: 'items', action: 'create'}, {path: 'items', method: :post})
  19. #
  20. # You can also pass in +extras+ with a hash containing URL parameters that would normally be in the query string. This can be used
  21. # to assert that values in the query string will end up in the params hash correctly. To test query strings you must use the extras
  22. # argument because appending the query string on the path directly will not work. For example:
  23. #
  24. # # Asserts that a path of '/items/list/1?view=print' returns the correct options
  25. # assert_recognizes({controller: 'items', action: 'list', id: '1', view: 'print'}, 'items/list/1', { view: "print" })
  26. #
  27. # The +message+ parameter allows you to pass in an error message that is displayed upon failure.
  28. #
  29. # # Check the default route (i.e., the index action)
  30. # assert_recognizes({controller: 'items', action: 'index'}, 'items')
  31. #
  32. # # Test a specific action
  33. # assert_recognizes({controller: 'items', action: 'list'}, 'items/list')
  34. #
  35. # # Test an action with a parameter
  36. # assert_recognizes({controller: 'items', action: 'destroy', id: '1'}, 'items/destroy/1')
  37. #
  38. # # Test a custom route
  39. # assert_recognizes({controller: 'items', action: 'show', id: '1'}, 'view/item1')
  40. 1 def assert_recognizes(expected_options, path, extras = {}, msg = nil)
  41. if path.is_a?(Hash) && path[:method].to_s == "all"
  42. [:get, :post, :put, :delete].each do |method|
  43. assert_recognizes(expected_options, path.merge(method: method), extras, msg)
  44. end
  45. else
  46. request = recognized_request_for(path, extras, msg)
  47. expected_options = expected_options.clone
  48. expected_options.stringify_keys!
  49. msg = message(msg, "") {
  50. sprintf("The recognized options <%s> did not match <%s>, difference:",
  51. request.path_parameters, expected_options)
  52. }
  53. assert_equal(expected_options, request.path_parameters, msg)
  54. end
  55. end
  56. # Asserts that the provided options can be used to generate the provided path. This is the inverse of +assert_recognizes+.
  57. # The +extras+ parameter is used to tell the request the names and values of additional request parameters that would be in
  58. # a query string. The +message+ parameter allows you to specify a custom error message for assertion failures.
  59. #
  60. # The +defaults+ parameter is unused.
  61. #
  62. # # Asserts that the default action is generated for a route with no action
  63. # assert_generates "/items", controller: "items", action: "index"
  64. #
  65. # # Tests that the list action is properly routed
  66. # assert_generates "/items/list", controller: "items", action: "list"
  67. #
  68. # # Tests the generation of a route with a parameter
  69. # assert_generates "/items/list/1", { controller: "items", action: "list", id: "1" }
  70. #
  71. # # Asserts that the generated route gives us our custom route
  72. # assert_generates "changesets/12", { controller: 'scm', action: 'show_diff', revision: "12" }
  73. 1 def assert_generates(expected_path, options, defaults = {}, extras = {}, message = nil)
  74. if expected_path =~ %r{://}
  75. fail_on(URI::InvalidURIError, message) do
  76. uri = URI.parse(expected_path)
  77. expected_path = uri.path.to_s.empty? ? "/" : uri.path
  78. end
  79. else
  80. expected_path = "/#{expected_path}" unless expected_path.first == "/"
  81. end
  82. # Load routes.rb if it hasn't been loaded.
  83. options = options.clone
  84. generated_path, query_string_keys = @routes.generate_extras(options, defaults)
  85. found_extras = options.reject { |k, _| ! query_string_keys.include? k }
  86. msg = message || sprintf("found extras <%s>, not <%s>", found_extras, extras)
  87. assert_equal(extras, found_extras, msg)
  88. msg = message || sprintf("The generated path <%s> did not match <%s>", generated_path,
  89. expected_path)
  90. assert_equal(expected_path, generated_path, msg)
  91. end
  92. # Asserts that path and options match both ways; in other words, it verifies that <tt>path</tt> generates
  93. # <tt>options</tt> and then that <tt>options</tt> generates <tt>path</tt>. This essentially combines +assert_recognizes+
  94. # and +assert_generates+ into one step.
  95. #
  96. # The +extras+ hash allows you to specify options that would normally be provided as a query string to the action. The
  97. # +message+ parameter allows you to specify a custom error message to display upon failure.
  98. #
  99. # # Asserts a basic route: a controller with the default action (index)
  100. # assert_routing '/home', controller: 'home', action: 'index'
  101. #
  102. # # Test a route generated with a specific controller, action, and parameter (id)
  103. # assert_routing '/entries/show/23', controller: 'entries', action: 'show', id: 23
  104. #
  105. # # Asserts a basic route (controller + default action), with an error message if it fails
  106. # assert_routing '/store', { controller: 'store', action: 'index' }, {}, {}, 'Route for store index not generated properly'
  107. #
  108. # # Tests a route, providing a defaults hash
  109. # assert_routing 'controller/action/9', {id: "9", item: "square"}, {controller: "controller", action: "action"}, {}, {item: "square"}
  110. #
  111. # # Tests a route with an HTTP method
  112. # assert_routing({ method: 'put', path: '/product/321' }, { controller: "product", action: "update", id: "321" })
  113. 1 def assert_routing(path, options, defaults = {}, extras = {}, message = nil)
  114. assert_recognizes(options, path, extras, message)
  115. controller, default_controller = options[:controller], defaults[:controller]
  116. if controller && controller.include?(?/) && default_controller && default_controller.include?(?/)
  117. options[:controller] = "/#{controller}"
  118. end
  119. generate_options = options.dup.delete_if { |k, _| defaults.key?(k) }
  120. assert_generates(path.is_a?(Hash) ? path[:path] : path, generate_options, defaults, extras, message)
  121. end
  122. # A helper to make it easier to test different route configurations.
  123. # This method temporarily replaces @routes with a new RouteSet instance.
  124. #
  125. # The new instance is yielded to the passed block. Typically the block
  126. # will create some routes using <tt>set.draw { match ... }</tt>:
  127. #
  128. # with_routing do |set|
  129. # set.draw do
  130. # resources :users
  131. # end
  132. # assert_equal "/users", users_path
  133. # end
  134. #
  135. 1 def with_routing
  136. old_routes, @routes = @routes, ActionDispatch::Routing::RouteSet.new
  137. if defined?(@controller) && @controller
  138. old_controller, @controller = @controller, @controller.clone
  139. _routes = @routes
  140. @controller.singleton_class.include(_routes.url_helpers)
  141. if @controller.respond_to? :view_context_class
  142. @controller.view_context_class = Class.new(@controller.view_context_class) do
  143. include _routes.url_helpers
  144. end
  145. end
  146. end
  147. yield @routes
  148. ensure
  149. @routes = old_routes
  150. if defined?(@controller) && @controller
  151. @controller = old_controller
  152. end
  153. end
  154. # ROUTES TODO: These assertions should really work in an integration context
  155. 1 def method_missing(selector, *args, &block)
  156. if defined?(@controller) && @controller && defined?(@routes) && @routes && @routes.named_routes.route_defined?(selector)
  157. @controller.send(selector, *args, &block)
  158. else
  159. super
  160. end
  161. end
  162. 1 private
  163. # Recognizes the route for a given path.
  164. 1 def recognized_request_for(path, extras = {}, msg)
  165. if path.is_a?(Hash)
  166. method = path[:method]
  167. path = path[:path]
  168. else
  169. method = :get
  170. end
  171. request = ActionController::TestRequest.create @controller.class
  172. if path =~ %r{://}
  173. fail_on(URI::InvalidURIError, msg) do
  174. uri = URI.parse(path)
  175. request.env["rack.url_scheme"] = uri.scheme || "http"
  176. request.host = uri.host if uri.host
  177. request.port = uri.port if uri.port
  178. request.path = uri.path.to_s.empty? ? "/" : uri.path
  179. end
  180. else
  181. path = "/#{path}" unless path.first == "/"
  182. request.path = path
  183. end
  184. request.request_method = method if method
  185. params = fail_on(ActionController::RoutingError, msg) do
  186. @routes.recognize_path(path, method: method, extras: extras)
  187. end
  188. request.path_parameters = params.with_indifferent_access
  189. request
  190. end
  191. 1 def fail_on(exception_class, message)
  192. yield
  193. rescue exception_class => e
  194. raise Minitest::Assertion, message || e.message
  195. end
  196. end
  197. end
  198. end

target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/testing/integration.rb

85.64% lines covered

202 relevant lines. 173 lines covered and 29 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "stringio"
  3. 1 require "uri"
  4. 1 require "active_support/core_ext/kernel/singleton_class"
  5. 1 require "active_support/core_ext/object/try"
  6. 1 require "rack/test"
  7. 1 require "minitest"
  8. 1 require "action_dispatch/testing/request_encoder"
  9. 1 module ActionDispatch
  10. 1 module Integration #:nodoc:
  11. 1 module RequestHelpers
  12. # Performs a GET request with the given parameters. See ActionDispatch::Integration::Session#process
  13. # for more details.
  14. 1 def get(path, **args)
  15. 2 process(:get, path, **args)
  16. end
  17. # Performs a POST request with the given parameters. See ActionDispatch::Integration::Session#process
  18. # for more details.
  19. 1 def post(path, **args)
  20. process(:post, path, **args)
  21. end
  22. # Performs a PATCH request with the given parameters. See ActionDispatch::Integration::Session#process
  23. # for more details.
  24. 1 def patch(path, **args)
  25. process(:patch, path, **args)
  26. end
  27. # Performs a PUT request with the given parameters. See ActionDispatch::Integration::Session#process
  28. # for more details.
  29. 1 def put(path, **args)
  30. process(:put, path, **args)
  31. end
  32. # Performs a DELETE request with the given parameters. See ActionDispatch::Integration::Session#process
  33. # for more details.
  34. 1 def delete(path, **args)
  35. process(:delete, path, **args)
  36. end
  37. # Performs a HEAD request with the given parameters. See ActionDispatch::Integration::Session#process
  38. # for more details.
  39. 1 def head(path, *args)
  40. process(:head, path, *args)
  41. end
  42. # Follow a single redirect response. If the last response was not a
  43. # redirect, an exception will be raised. Otherwise, the redirect is
  44. # performed on the location header.
  45. 1 def follow_redirect!
  46. raise "not a redirect! #{status} #{status_message}" unless redirect?
  47. get(response.location)
  48. status
  49. end
  50. end
  51. # An instance of this class represents a set of requests and responses
  52. # performed sequentially by a test process. Because you can instantiate
  53. # multiple sessions and run them side-by-side, you can also mimic (to some
  54. # limited extent) multiple simultaneous users interacting with your system.
  55. #
  56. # Typically, you will instantiate a new session using
  57. # IntegrationTest#open_session, rather than instantiating
  58. # Integration::Session directly.
  59. 1 class Session
  60. 1 DEFAULT_HOST = "www.example.com"
  61. 1 include Minitest::Assertions
  62. 1 include TestProcess, RequestHelpers, Assertions
  63. 1 %w( status status_message headers body redirect? ).each do |method|
  64. 5 delegate method, to: :response, allow_nil: true
  65. end
  66. 1 %w( path ).each do |method|
  67. 1 delegate method, to: :request, allow_nil: true
  68. end
  69. # The hostname used in the last request.
  70. 1 def host
  71. 9 @host || DEFAULT_HOST
  72. end
  73. 1 attr_writer :host
  74. # The remote_addr used in the last request.
  75. 1 attr_accessor :remote_addr
  76. # The Accept header to send.
  77. 1 attr_accessor :accept
  78. # A map of the cookies returned by the last response, and which will be
  79. # sent with the next request.
  80. 1 def cookies
  81. _mock_session.cookie_jar
  82. end
  83. # A reference to the controller instance used by the last request.
  84. 1 attr_reader :controller
  85. # A reference to the request instance used by the last request.
  86. 1 attr_reader :request
  87. # A reference to the response instance used by the last request.
  88. 1 attr_reader :response
  89. # A running counter of the number of requests processed.
  90. 1 attr_accessor :request_count
  91. 1 include ActionDispatch::Routing::UrlFor
  92. # Create and initialize a new Session instance.
  93. 1 def initialize(app)
  94. 2 super()
  95. 2 @app = app
  96. 2 reset!
  97. end
  98. 1 def url_options
  99. 3 @url_options ||= default_url_options.dup.tap do |url_options|
  100. 3 url_options.reverse_merge!(controller.url_options) if controller
  101. 3 if @app.respond_to?(:routes)
  102. 3 url_options.reverse_merge!(@app.routes.default_url_options)
  103. end
  104. 3 url_options.reverse_merge!(host: host, protocol: https? ? "https" : "http")
  105. end
  106. end
  107. # Resets the instance. This can be used to reset the state information
  108. # in an existing session instance, so it can be used from a clean-slate
  109. # condition.
  110. #
  111. # session.reset!
  112. 1 def reset!
  113. 2 @https = false
  114. 2 @controller = @request = @response = nil
  115. 2 @_mock_session = nil
  116. 2 @request_count = 0
  117. 2 @url_options = nil
  118. 2 self.host = DEFAULT_HOST
  119. 2 self.remote_addr = "127.0.0.1"
  120. 2 self.accept = "text/xml,application/xml,application/xhtml+xml," \
  121. "text/html;q=0.9,text/plain;q=0.8,image/png," \
  122. "*/*;q=0.5"
  123. 2 unless defined? @named_routes_configured
  124. # the helpers are made protected by default--we make them public for
  125. # easier access during testing and troubleshooting.
  126. 2 @named_routes_configured = true
  127. end
  128. end
  129. # Specify whether or not the session should mimic a secure HTTPS request.
  130. #
  131. # session.https!
  132. # session.https!(false)
  133. 1 def https!(flag = true)
  134. 2 @https = flag
  135. end
  136. # Returns +true+ if the session is mimicking a secure HTTPS request.
  137. #
  138. # if session.https?
  139. # ...
  140. # end
  141. 1 def https?
  142. 9 @https
  143. end
  144. # Performs the actual request.
  145. #
  146. # - +method+: The HTTP method (GET, POST, PATCH, PUT, DELETE, HEAD, OPTIONS)
  147. # as a symbol.
  148. # - +path+: The URI (as a String) on which you want to perform the
  149. # request.
  150. # - +params+: The HTTP parameters that you want to pass. This may
  151. # be +nil+,
  152. # a Hash, or a String that is appropriately encoded
  153. # (<tt>application/x-www-form-urlencoded</tt> or
  154. # <tt>multipart/form-data</tt>).
  155. # - +headers+: Additional headers to pass, as a Hash. The headers will be
  156. # merged into the Rack env hash.
  157. # - +env+: Additional env to pass, as a Hash. The headers will be
  158. # merged into the Rack env hash.
  159. #
  160. # This method is rarely used directly. Use +#get+, +#post+, or other standard
  161. # HTTP methods in integration tests. +#process+ is only required when using a
  162. # request method that doesn't have a method defined in the integration tests.
  163. #
  164. # This method returns the response status, after performing the request.
  165. # Furthermore, if this method was called from an ActionDispatch::IntegrationTest object,
  166. # then that object's <tt>@response</tt> instance variable will point to a Response object
  167. # which one can use to inspect the details of the response.
  168. #
  169. # Example:
  170. # process :get, '/author', params: { since: 201501011400 }
  171. 1 def process(method, path, params: nil, headers: nil, env: nil, xhr: false, as: nil)
  172. 2 request_encoder = RequestEncoder.encoder(as)
  173. 2 headers ||= {}
  174. 2 if method == :get && as == :json && params
  175. headers["X-Http-Method-Override"] = "GET"
  176. method = :post
  177. end
  178. 2 if path =~ %r{://}
  179. 2 path = build_expanded_path(path) do |location|
  180. 2 https! URI::HTTPS === location if location.scheme
  181. 2 if url_host = location.host
  182. 2 default = Rack::Request::DEFAULT_PORTS[location.scheme]
  183. 2 url_host += ":#{location.port}" if default != location.port
  184. 2 host! url_host
  185. end
  186. end
  187. end
  188. 2 hostname, port = host.split(":")
  189. request_env = {
  190. :method => method,
  191. 1 :params => request_encoder.encode_params(params),
  192. "SERVER_NAME" => hostname,
  193. 2 "SERVER_PORT" => port || (https? ? "443" : "80"),
  194. 1 "HTTPS" => https? ? "on" : "off",
  195. 1 "rack.url_scheme" => https? ? "https" : "http",
  196. "REQUEST_URI" => path,
  197. 1 "HTTP_HOST" => host,
  198. 1 "REMOTE_ADDR" => remote_addr,
  199. 1 "CONTENT_TYPE" => request_encoder.content_type,
  200. 1 "HTTP_ACCEPT" => request_encoder.accept_header || accept
  201. }
  202. 2 wrapped_headers = Http::Headers.from_hash({})
  203. 2 wrapped_headers.merge!(headers) if headers
  204. 2 if xhr
  205. wrapped_headers["HTTP_X_REQUESTED_WITH"] = "XMLHttpRequest"
  206. wrapped_headers["HTTP_ACCEPT"] ||= [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(", ")
  207. end
  208. # This modifies the passed request_env directly.
  209. 2 if wrapped_headers.present?
  210. 2 Http::Headers.from_hash(request_env).merge!(wrapped_headers)
  211. end
  212. 2 if env.present?
  213. Http::Headers.from_hash(request_env).merge!(env)
  214. end
  215. 2 session = Rack::Test::Session.new(_mock_session)
  216. # NOTE: rack-test v0.5 doesn't build a default uri correctly
  217. # Make sure requested path is always a full URI.
  218. 2 session.request(build_full_uri(path, request_env), request_env)
  219. 2 @request_count += 1
  220. 2 @request = ActionDispatch::Request.new(session.last_request.env)
  221. 2 response = _mock_session.last_response
  222. 2 @response = ActionDispatch::TestResponse.from_response(response)
  223. 2 @response.request = @request
  224. 2 @html_document = nil
  225. 2 @url_options = nil
  226. 2 @controller = @request.controller_instance
  227. 2 response.status
  228. end
  229. # Set the host name to use in the next request.
  230. #
  231. # session.host! "www.example.com"
  232. 1 alias :host! :host=
  233. 1 private
  234. 1 def _mock_session
  235. 4 @_mock_session ||= Rack::MockSession.new(@app, host)
  236. end
  237. 1 def build_full_uri(path, env)
  238. 2 "#{env['rack.url_scheme']}://#{env['SERVER_NAME']}:#{env['SERVER_PORT']}#{path}"
  239. end
  240. 1 def build_expanded_path(path)
  241. 2 location = URI.parse(path)
  242. 2 yield location if block_given?
  243. 2 path = location.path
  244. 2 location.query ? "#{path}?#{location.query}" : path
  245. end
  246. end
  247. 1 module Runner
  248. 1 include ActionDispatch::Assertions
  249. 1 APP_SESSIONS = {}
  250. 1 attr_reader :app
  251. 1 def initialize(*args, &blk)
  252. 2 super(*args, &blk)
  253. 2 @integration_session = nil
  254. end
  255. 1 def before_setup # :nodoc:
  256. 2 @app = nil
  257. 2 super
  258. end
  259. 1 def integration_session
  260. 8 @integration_session ||= create_session(app)
  261. end
  262. # Reset the current session. This is useful for testing multiple sessions
  263. # in a single test case.
  264. 1 def reset!
  265. @integration_session = create_session(app)
  266. end
  267. 1 def create_session(app)
  268. 2 klass = APP_SESSIONS[app] ||= Class.new(Integration::Session) {
  269. # If the app is a Rails app, make url_helpers available on the session.
  270. # This makes app.url_for and app.foo_path available in the console.
  271. 1 if app.respond_to?(:routes)
  272. 1 include app.routes.url_helpers
  273. 1 include app.routes.mounted_helpers
  274. end
  275. }
  276. 2 klass.new(app)
  277. end
  278. 1 def remove! # :nodoc:
  279. @integration_session = nil
  280. end
  281. 1 %w(get post patch put head delete cookies assigns follow_redirect!).each do |method|
  282. 9 define_method(method) do |*args|
  283. # reset the html_document variable, except for cookies/assigns calls
  284. 2 unless method == "cookies" || method == "assigns"
  285. 2 @html_document = nil
  286. end
  287. 2 integration_session.__send__(method, *args).tap do
  288. 2 copy_session_variables!
  289. end
  290. end
  291. end
  292. # Open a new session instance. If a block is given, the new session is
  293. # yielded to the block before being returned.
  294. #
  295. # session = open_session do |sess|
  296. # sess.extend(CustomAssertions)
  297. # end
  298. #
  299. # By default, a single session is automatically created for you, but you
  300. # can use this method to open multiple sessions that ought to be tested
  301. # simultaneously.
  302. 1 def open_session
  303. dup.tap do |session|
  304. session.reset!
  305. yield session if block_given?
  306. end
  307. end
  308. # Copy the instance variables from the current session instance into the
  309. # test instance.
  310. 1 def copy_session_variables! #:nodoc:
  311. 5 @controller = @integration_session.controller
  312. 5 @response = @integration_session.response
  313. 5 @request = @integration_session.request
  314. end
  315. 1 def default_url_options
  316. integration_session.default_url_options
  317. end
  318. 1 def default_url_options=(options)
  319. integration_session.default_url_options = options
  320. end
  321. 1 private
  322. 1 def respond_to_missing?(method, _)
  323. integration_session.respond_to?(method) || super
  324. end
  325. # Delegate unhandled messages to the current session instance.
  326. 1 def method_missing(method, *args, &block)
  327. 3 if integration_session.respond_to?(method)
  328. 3 integration_session.public_send(method, *args, &block).tap do
  329. 3 copy_session_variables!
  330. end
  331. else
  332. super
  333. end
  334. end
  335. end
  336. end
  337. # An integration test spans multiple controllers and actions,
  338. # tying them all together to ensure they work together as expected. It tests
  339. # more completely than either unit or functional tests do, exercising the
  340. # entire stack, from the dispatcher to the database.
  341. #
  342. # At its simplest, you simply extend <tt>IntegrationTest</tt> and write your tests
  343. # using the get/post methods:
  344. #
  345. # require "test_helper"
  346. #
  347. # class ExampleTest < ActionDispatch::IntegrationTest
  348. # fixtures :people
  349. #
  350. # def test_login
  351. # # get the login page
  352. # get "/login"
  353. # assert_equal 200, status
  354. #
  355. # # post the login and follow through to the home page
  356. # post "/login", params: { username: people(:jamis).username,
  357. # password: people(:jamis).password }
  358. # follow_redirect!
  359. # assert_equal 200, status
  360. # assert_equal "/home", path
  361. # end
  362. # end
  363. #
  364. # However, you can also have multiple session instances open per test, and
  365. # even extend those instances with assertions and methods to create a very
  366. # powerful testing DSL that is specific for your application. You can even
  367. # reference any named routes you happen to have defined.
  368. #
  369. # require "test_helper"
  370. #
  371. # class AdvancedTest < ActionDispatch::IntegrationTest
  372. # fixtures :people, :rooms
  373. #
  374. # def test_login_and_speak
  375. # jamis, david = login(:jamis), login(:david)
  376. # room = rooms(:office)
  377. #
  378. # jamis.enter(room)
  379. # jamis.speak(room, "anybody home?")
  380. #
  381. # david.enter(room)
  382. # david.speak(room, "hello!")
  383. # end
  384. #
  385. # private
  386. #
  387. # module CustomAssertions
  388. # def enter(room)
  389. # # reference a named route, for maximum internal consistency!
  390. # get(room_url(id: room.id))
  391. # assert(...)
  392. # ...
  393. # end
  394. #
  395. # def speak(room, message)
  396. # post "/say/#{room.id}", xhr: true, params: { message: message }
  397. # assert(...)
  398. # ...
  399. # end
  400. # end
  401. #
  402. # def login(who)
  403. # open_session do |sess|
  404. # sess.extend(CustomAssertions)
  405. # who = people(who)
  406. # sess.post "/login", params: { username: who.username,
  407. # password: who.password }
  408. # assert(...)
  409. # end
  410. # end
  411. # end
  412. #
  413. # Another longer example would be:
  414. #
  415. # A simple integration test that exercises multiple controllers:
  416. #
  417. # require 'test_helper'
  418. #
  419. # class UserFlowsTest < ActionDispatch::IntegrationTest
  420. # test "login and browse site" do
  421. # # login via https
  422. # https!
  423. # get "/login"
  424. # assert_response :success
  425. #
  426. # post "/login", params: { username: users(:david).username, password: users(:david).password }
  427. # follow_redirect!
  428. # assert_equal '/welcome', path
  429. # assert_equal 'Welcome david!', flash[:notice]
  430. #
  431. # https!(false)
  432. # get "/articles/all"
  433. # assert_response :success
  434. # assert_select 'h1', 'Articles'
  435. # end
  436. # end
  437. #
  438. # As you can see the integration test involves multiple controllers and
  439. # exercises the entire stack from database to dispatcher. In addition you can
  440. # have multiple session instances open simultaneously in a test and extend
  441. # those instances with assertion methods to create a very powerful testing
  442. # DSL (domain-specific language) just for your application.
  443. #
  444. # Here's an example of multiple sessions and custom DSL in an integration test
  445. #
  446. # require 'test_helper'
  447. #
  448. # class UserFlowsTest < ActionDispatch::IntegrationTest
  449. # test "login and browse site" do
  450. # # User david logs in
  451. # david = login(:david)
  452. # # User guest logs in
  453. # guest = login(:guest)
  454. #
  455. # # Both are now available in different sessions
  456. # assert_equal 'Welcome david!', david.flash[:notice]
  457. # assert_equal 'Welcome guest!', guest.flash[:notice]
  458. #
  459. # # User david can browse site
  460. # david.browses_site
  461. # # User guest can browse site as well
  462. # guest.browses_site
  463. #
  464. # # Continue with other assertions
  465. # end
  466. #
  467. # private
  468. #
  469. # module CustomDsl
  470. # def browses_site
  471. # get "/products/all"
  472. # assert_response :success
  473. # assert_select 'h1', 'Products'
  474. # end
  475. # end
  476. #
  477. # def login(user)
  478. # open_session do |sess|
  479. # sess.extend(CustomDsl)
  480. # u = users(user)
  481. # sess.https!
  482. # sess.post "/login", params: { username: u.username, password: u.password }
  483. # assert_equal '/welcome', sess.path
  484. # sess.https!(false)
  485. # end
  486. # end
  487. # end
  488. #
  489. # See the {request helpers documentation}[rdoc-ref:ActionDispatch::Integration::RequestHelpers] for help on how to
  490. # use +get+, etc.
  491. #
  492. # === Changing the request encoding
  493. #
  494. # You can also test your JSON API easily by setting what the request should
  495. # be encoded as:
  496. #
  497. # require "test_helper"
  498. #
  499. # class ApiTest < ActionDispatch::IntegrationTest
  500. # test "creates articles" do
  501. # assert_difference -> { Article.count } do
  502. # post articles_path, params: { article: { title: "Ahoy!" } }, as: :json
  503. # end
  504. #
  505. # assert_response :success
  506. # assert_equal({ id: Article.last.id, title: "Ahoy!" }, response.parsed_body)
  507. # end
  508. # end
  509. #
  510. # The +as+ option passes an "application/json" Accept header (thereby setting
  511. # the request format to JSON unless overridden), sets the content type to
  512. # "application/json" and encodes the parameters as JSON.
  513. #
  514. # Calling +parsed_body+ on the response parses the response body based on the
  515. # last response MIME type.
  516. #
  517. # Out of the box, only <tt>:json</tt> is supported. But for any custom MIME
  518. # types you've registered, you can add your own encoders with:
  519. #
  520. # ActionDispatch::IntegrationTest.register_encoder :wibble,
  521. # param_encoder: -> params { params.to_wibble },
  522. # response_parser: -> body { body }
  523. #
  524. # Where +param_encoder+ defines how the params should be encoded and
  525. # +response_parser+ defines how the response body should be parsed through
  526. # +parsed_body+.
  527. #
  528. # Consult the Rails Testing Guide for more.
  529. 1 class IntegrationTest < ActiveSupport::TestCase
  530. 1 include TestProcess::FixtureFile
  531. 1 module UrlOptions
  532. 1 extend ActiveSupport::Concern
  533. 1 def url_options
  534. integration_session.url_options
  535. end
  536. end
  537. 1 module Behavior
  538. 1 extend ActiveSupport::Concern
  539. 1 include Integration::Runner
  540. 1 include ActionController::TemplateAssertions
  541. 1 included do
  542. 1 include ActionDispatch::Routing::UrlFor
  543. 1 include UrlOptions # don't let UrlFor override the url_options method
  544. 1 ActiveSupport.run_load_hooks(:action_dispatch_integration_test, self)
  545. 1 @@app = nil
  546. end
  547. 1 module ClassMethods
  548. 1 def app
  549. 2 if defined?(@@app) && @@app
  550. @@app
  551. else
  552. 2 ActionDispatch.test_app
  553. end
  554. end
  555. 1 def app=(app)
  556. @@app = app
  557. end
  558. 1 def register_encoder(*args)
  559. RequestEncoder.register_encoder(*args)
  560. end
  561. end
  562. 1 def app
  563. 2 super || self.class.app
  564. end
  565. 1 def document_root_element
  566. html_document.root
  567. end
  568. end
  569. 1 include Behavior
  570. end
  571. end

target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/testing/request_encoder.rb

83.33% lines covered

30 relevant lines. 25 lines covered and 5 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActionDispatch
  3. 1 class RequestEncoder # :nodoc:
  4. 1 class IdentityEncoder
  5. 1 def content_type; end
  6. 1 def accept_header; end
  7. 3 def encode_params(params); params; end
  8. 3 def response_parser; -> body { body }; end
  9. end
  10. 1 @encoders = { identity: IdentityEncoder.new }
  11. 1 attr_reader :response_parser
  12. 1 def initialize(mime_name, param_encoder, response_parser)
  13. 1 @mime = Mime[mime_name]
  14. 1 unless @mime
  15. raise ArgumentError, "Can't register a request encoder for " \
  16. "unregistered MIME Type: #{mime_name}. See `Mime::Type.register`."
  17. end
  18. 1 @response_parser = response_parser || -> body { body }
  19. 1 @param_encoder = param_encoder || :"to_#{@mime.symbol}".to_proc
  20. end
  21. 1 def content_type
  22. @mime.to_s
  23. end
  24. 1 def accept_header
  25. @mime.to_s
  26. end
  27. 1 def encode_params(params)
  28. @param_encoder.call(params) if params
  29. end
  30. 1 def self.parser(content_type)
  31. 2 mime = Mime::Type.lookup(content_type)
  32. 2 encoder(mime ? mime.ref : nil).response_parser
  33. end
  34. 1 def self.encoder(name)
  35. 4 @encoders[name] || @encoders[:identity]
  36. end
  37. 1 def self.register_encoder(mime_name, param_encoder: nil, response_parser: nil)
  38. 1 @encoders[mime_name] = new(mime_name, param_encoder, response_parser)
  39. end
  40. 1 register_encoder :json, response_parser: -> body { JSON.parse(body) }
  41. end
  42. end

target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/testing/test_process.rb

60.0% lines covered

20 relevant lines. 12 lines covered and 8 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "action_dispatch/middleware/cookies"
  3. 1 require "action_dispatch/middleware/flash"
  4. 1 module ActionDispatch
  5. 1 module TestProcess
  6. 1 module FixtureFile
  7. # Shortcut for <tt>Rack::Test::UploadedFile.new(File.join(ActionDispatch::IntegrationTest.fixture_path, path), type)</tt>:
  8. #
  9. # post :change_avatar, avatar: fixture_file_upload('files/spongebob.png', 'image/png')
  10. #
  11. # To upload binary files on Windows, pass <tt>:binary</tt> as the last parameter.
  12. # This will not affect other platforms:
  13. #
  14. # post :change_avatar, avatar: fixture_file_upload('files/spongebob.png', 'image/png', :binary)
  15. 1 def fixture_file_upload(path, mime_type = nil, binary = false)
  16. if self.class.respond_to?(:fixture_path) && self.class.fixture_path &&
  17. !File.exist?(path)
  18. path = File.join(self.class.fixture_path, path)
  19. end
  20. Rack::Test::UploadedFile.new(path, mime_type, binary)
  21. end
  22. end
  23. 1 include FixtureFile
  24. 1 def assigns(key = nil)
  25. raise NoMethodError,
  26. "assigns has been extracted to a gem. To continue using it,
  27. add `gem 'rails-controller-testing'` to your Gemfile."
  28. end
  29. 1 def session
  30. @request.session
  31. end
  32. 1 def flash
  33. @request.flash
  34. end
  35. 1 def cookies
  36. @cookie_jar ||= Cookies::CookieJar.build(@request, @request.cookies)
  37. end
  38. 1 def redirect_to_url
  39. @response.redirect_url
  40. end
  41. end
  42. end

target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/testing/test_request.rb

54.29% lines covered

35 relevant lines. 19 lines covered and 16 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/hash/indifferent_access"
  3. 1 require "rack/utils"
  4. 1 module ActionDispatch
  5. 1 class TestRequest < Request
  6. 1 DEFAULT_ENV = Rack::MockRequest.env_for("/",
  7. "HTTP_HOST" => "test.host",
  8. "REMOTE_ADDR" => "0.0.0.0",
  9. "HTTP_USER_AGENT" => "Rails Testing",
  10. )
  11. # Create a new test request with default +env+ values.
  12. 1 def self.create(env = {})
  13. env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
  14. env["rack.request.cookie_hash"] ||= {}.with_indifferent_access
  15. new(default_env.merge(env))
  16. end
  17. 1 def self.default_env
  18. DEFAULT_ENV
  19. end
  20. 1 private_class_method :default_env
  21. 1 def request_method=(method)
  22. super(method.to_s.upcase)
  23. end
  24. 1 def host=(host)
  25. set_header("HTTP_HOST", host)
  26. end
  27. 1 def port=(number)
  28. set_header("SERVER_PORT", number.to_i)
  29. end
  30. 1 def request_uri=(uri)
  31. set_header("REQUEST_URI", uri)
  32. end
  33. 1 def path=(path)
  34. set_header("PATH_INFO", path)
  35. end
  36. 1 def action=(action_name)
  37. path_parameters[:action] = action_name.to_s
  38. end
  39. 1 def if_modified_since=(last_modified)
  40. set_header("HTTP_IF_MODIFIED_SINCE", last_modified)
  41. end
  42. 1 def if_none_match=(etag)
  43. set_header("HTTP_IF_NONE_MATCH", etag)
  44. end
  45. 1 def remote_addr=(addr)
  46. set_header("REMOTE_ADDR", addr)
  47. end
  48. 1 def user_agent=(user_agent)
  49. set_header("HTTP_USER_AGENT", user_agent)
  50. end
  51. 1 def accept=(mime_types)
  52. delete_header("action_dispatch.request.accepts")
  53. set_header("HTTP_ACCEPT", Array(mime_types).collect(&:to_s).join(","))
  54. end
  55. end
  56. end

target/rubygems/gems/actionpack-5.2.3/lib/action_dispatch/testing/test_response.rb

63.16% lines covered

19 relevant lines. 12 lines covered and 7 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "action_dispatch/testing/request_encoder"
  3. 1 module ActionDispatch
  4. # Integration test methods such as ActionDispatch::Integration::Session#get
  5. # and ActionDispatch::Integration::Session#post return objects of class
  6. # TestResponse, which represent the HTTP response results of the requested
  7. # controller actions.
  8. #
  9. # See Response for more information on controller response objects.
  10. 1 class TestResponse < Response
  11. 1 def self.from_response(response)
  12. 2 new response.status, response.headers, response.body
  13. end
  14. 1 def initialize(*) # :nodoc:
  15. 2 super
  16. 2 @response_parser = RequestEncoder.parser(content_type)
  17. end
  18. # Was the response successful?
  19. 1 def success?
  20. ActiveSupport::Deprecation.warn(<<-MSG.squish)
  21. The success? predicate is deprecated and will be removed in Rails 6.0.
  22. Please use successful? as provided by Rack::Response::Helpers.
  23. MSG
  24. successful?
  25. end
  26. # Was the URL not found?
  27. 1 def missing?
  28. ActiveSupport::Deprecation.warn(<<-MSG.squish)
  29. The missing? predicate is deprecated and will be removed in Rails 6.0.
  30. Please use not_found? as provided by Rack::Response::Helpers.
  31. MSG
  32. not_found?
  33. end
  34. # Was there a server-side error?
  35. 1 def error?
  36. ActiveSupport::Deprecation.warn(<<-MSG.squish)
  37. The error? predicate is deprecated and will be removed in Rails 6.0.
  38. Please use server_error? as provided by Rack::Response::Helpers.
  39. MSG
  40. server_error?
  41. end
  42. 1 def parsed_body
  43. @parsed_body ||= @response_parser.call(body)
  44. end
  45. end
  46. end

target/rubygems/gems/actionview-5.2.3/lib/action_view/buffers.rb

70.0% lines covered

30 relevant lines. 21 lines covered and 9 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/string/output_safety"
  3. 1 module ActionView
  4. 1 class OutputBuffer < ActiveSupport::SafeBuffer #:nodoc:
  5. 1 def initialize(*)
  6. 5 super
  7. 5 encode!
  8. end
  9. 1 def <<(value)
  10. 13 return self if value.nil?
  11. 12 super(value.to_s)
  12. end
  13. 1 alias :append= :<<
  14. 1 def safe_expr_append=(val)
  15. return self if val.nil?
  16. safe_concat val.to_s
  17. end
  18. 1 alias :safe_append= :safe_concat
  19. end
  20. 1 class StreamingBuffer #:nodoc:
  21. 1 def initialize(block)
  22. @block = block
  23. end
  24. 1 def <<(value)
  25. value = value.to_s
  26. value = ERB::Util.h(value) unless value.html_safe?
  27. @block.call(value)
  28. end
  29. 1 alias :concat :<<
  30. 1 alias :append= :<<
  31. 1 def safe_concat(value)
  32. @block.call(value.to_s)
  33. end
  34. 1 alias :safe_append= :safe_concat
  35. 1 def html_safe?
  36. true
  37. end
  38. 1 def html_safe
  39. self
  40. end
  41. end
  42. end

target/rubygems/gems/actionview-5.2.3/lib/action_view/dependency_tracker.rb

51.32% lines covered

76 relevant lines. 39 lines covered and 37 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "concurrent/map"
  3. 1 require "action_view/path_set"
  4. 1 module ActionView
  5. 1 class DependencyTracker # :nodoc:
  6. 1 @trackers = Concurrent::Map.new
  7. 1 def self.find_dependencies(name, template, view_paths = nil)
  8. tracker = @trackers[template.handler]
  9. return [] unless tracker
  10. tracker.call(name, template, view_paths)
  11. end
  12. 1 def self.register_tracker(extension, tracker)
  13. 2 handler = Template.handler_for_extension(extension)
  14. 2 if tracker.respond_to?(:supports_view_paths?)
  15. 2 @trackers[handler] = tracker
  16. else
  17. @trackers[handler] = lambda { |name, template, _|
  18. tracker.call(name, template)
  19. }
  20. end
  21. end
  22. 1 def self.remove_tracker(handler)
  23. @trackers.delete(handler)
  24. end
  25. 1 class ERBTracker # :nodoc:
  26. 1 EXPLICIT_DEPENDENCY = /# Template Dependency: (\S+)/
  27. # A valid ruby identifier - suitable for class, method and specially variable names
  28. 1 IDENTIFIER = /
  29. [[:alpha:]_] # at least one uppercase letter, lowercase letter or underscore
  30. [[:word:]]* # followed by optional letters, numbers or underscores
  31. /x
  32. # Any kind of variable name. e.g. @instance, @@class, $global or local.
  33. # Possibly following a method call chain
  34. VARIABLE_OR_METHOD_CHAIN = /
  35. (?:\$|@{1,2})? # optional global, instance or class variable indicator
  36. 1 (?:#{IDENTIFIER}\.)* # followed by an optional chain of zero-argument method calls
  37. 1 (?<dynamic>#{IDENTIFIER}) # and a final valid identifier, captured as DYNAMIC
  38. /x
  39. # A simple string literal. e.g. "School's out!"
  40. 1 STRING = /
  41. (?<quote>['"]) # an opening quote
  42. (?<static>.*?) # with anything inside, captured as STATIC
  43. \k<quote> # and a matching closing quote
  44. /x
  45. # Part of any hash containing the :partial key
  46. 1 PARTIAL_HASH_KEY = /
  47. (?:\bpartial:|:partial\s*=>) # partial key in either old or new style hash syntax
  48. \s* # followed by optional spaces
  49. /x
  50. # Part of any hash containing the :layout key
  51. 1 LAYOUT_HASH_KEY = /
  52. (?:\blayout:|:layout\s*=>) # layout key in either old or new style hash syntax
  53. \s* # followed by optional spaces
  54. /x
  55. # Matches:
  56. # partial: "comments/comment", collection: @all_comments => "comments/comment"
  57. # (object: @single_comment, partial: "comments/comment") => "comments/comment"
  58. #
  59. # "comments/comments"
  60. # 'comments/comments'
  61. # ('comments/comments')
  62. #
  63. # (@topic) => "topics/topic"
  64. # topics => "topics/topic"
  65. # (message.topics) => "topics/topic"
  66. RENDER_ARGUMENTS = /\A
  67. (?:\s*\(?\s*) # optional opening paren surrounded by spaces
  68. 1 (?:.*?#{PARTIAL_HASH_KEY}|#{LAYOUT_HASH_KEY})? # optional hash, up to the partial or layout key declaration
  69. 1 (?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest
  70. /xm
  71. LAYOUT_DEPENDENCY = /\A
  72. (?:\s*\(?\s*) # optional opening paren surrounded by spaces
  73. 1 (?:.*?#{LAYOUT_HASH_KEY}) # check if the line has layout key declaration
  74. 1 (?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest
  75. /xm
  76. 1 def self.supports_view_paths? # :nodoc:
  77. true
  78. end
  79. 1 def self.call(name, template, view_paths = nil)
  80. new(name, template, view_paths).dependencies
  81. end
  82. 1 def initialize(name, template, view_paths = nil)
  83. @name, @template, @view_paths = name, template, view_paths
  84. end
  85. 1 def dependencies
  86. render_dependencies + explicit_dependencies
  87. end
  88. 1 attr_reader :name, :template
  89. 1 private :name, :template
  90. 1 private
  91. 1 def source
  92. template.source
  93. end
  94. 1 def directory
  95. name.split("/")[0..-2].join("/")
  96. end
  97. 1 def render_dependencies
  98. render_dependencies = []
  99. render_calls = source.split(/\brender\b/).drop(1)
  100. render_calls.each do |arguments|
  101. add_dependencies(render_dependencies, arguments, LAYOUT_DEPENDENCY)
  102. add_dependencies(render_dependencies, arguments, RENDER_ARGUMENTS)
  103. end
  104. render_dependencies.uniq
  105. end
  106. 1 def add_dependencies(render_dependencies, arguments, pattern)
  107. arguments.scan(pattern) do
  108. add_dynamic_dependency(render_dependencies, Regexp.last_match[:dynamic])
  109. add_static_dependency(render_dependencies, Regexp.last_match[:static])
  110. end
  111. end
  112. 1 def add_dynamic_dependency(dependencies, dependency)
  113. if dependency
  114. dependencies << "#{dependency.pluralize}/#{dependency.singularize}"
  115. end
  116. end
  117. 1 def add_static_dependency(dependencies, dependency)
  118. if dependency
  119. if dependency.include?("/")
  120. dependencies << dependency
  121. else
  122. dependencies << "#{directory}/#{dependency}"
  123. end
  124. end
  125. end
  126. 1 def resolve_directories(wildcard_dependencies)
  127. return [] unless @view_paths
  128. wildcard_dependencies.flat_map { |query, templates|
  129. @view_paths.find_all_with_query(query).map do |template|
  130. "#{File.dirname(query)}/#{File.basename(template).split('.').first}"
  131. end
  132. }.sort
  133. end
  134. 1 def explicit_dependencies
  135. dependencies = source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
  136. wildcards, explicits = dependencies.partition { |dependency| dependency[-1] == "*" }
  137. (explicits + resolve_directories(wildcards)).uniq
  138. end
  139. end
  140. 1 register_tracker :erb, ERBTracker
  141. end
  142. end

target/rubygems/gems/actionview-5.2.3/lib/action_view/digestor.rb

42.25% lines covered

71 relevant lines. 30 lines covered and 41 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "concurrent/map"
  3. 1 require "action_view/dependency_tracker"
  4. 1 require "monitor"
  5. 1 module ActionView
  6. 1 class Digestor
  7. 1 @@digest_mutex = Mutex.new
  8. 1 module PerExecutionDigestCacheExpiry
  9. 1 def self.before(target)
  10. 2 ActionView::LookupContext::DetailsKey.clear
  11. end
  12. end
  13. 1 class << self
  14. # Supported options:
  15. #
  16. # * <tt>name</tt> - Template name
  17. # * <tt>finder</tt> - An instance of <tt>ActionView::LookupContext</tt>
  18. # * <tt>dependencies</tt> - An array of dependent views
  19. 1 def digest(name:, finder:, dependencies: [])
  20. dependencies ||= []
  21. cache_key = [ name, finder.rendered_format, dependencies ].flatten.compact.join(".")
  22. # this is a correctly done double-checked locking idiom
  23. # (Concurrent::Map's lookups have volatile semantics)
  24. finder.digest_cache[cache_key] || @@digest_mutex.synchronize do
  25. finder.digest_cache.fetch(cache_key) do # re-check under lock
  26. partial = name.include?("/_")
  27. root = tree(name, finder, partial)
  28. dependencies.each do |injected_dep|
  29. root.children << Injected.new(injected_dep, nil, nil)
  30. end
  31. finder.digest_cache[cache_key] = root.digest(finder)
  32. end
  33. end
  34. end
  35. 1 def logger
  36. ActionView::Base.logger || NullLogger
  37. end
  38. # Create a dependency tree for template named +name+.
  39. 1 def tree(name, finder, partial = false, seen = {})
  40. logical_name = name.gsub(%r|/_|, "/")
  41. if template = find_template(finder, logical_name, [], partial, [])
  42. finder.rendered_format ||= template.formats.first
  43. if node = seen[template.identifier] # handle cycles in the tree
  44. node
  45. else
  46. node = seen[template.identifier] = Node.create(name, logical_name, template, partial)
  47. deps = DependencyTracker.find_dependencies(name, template, finder.view_paths)
  48. deps.uniq { |n| n.gsub(%r|/_|, "/") }.each do |dep_file|
  49. node.children << tree(dep_file, finder, true, seen)
  50. end
  51. node
  52. end
  53. else
  54. unless name.include?("#") # Dynamic template partial names can never be tracked
  55. logger.error " Couldn't find template for digesting: #{name}"
  56. end
  57. seen[name] ||= Missing.new(name, logical_name, nil)
  58. end
  59. end
  60. 1 private
  61. 1 def find_template(finder, name, prefixes, partial, keys)
  62. finder.disable_cache do
  63. format = finder.rendered_format
  64. result = finder.find_all(name, prefixes, partial, keys, formats: [format]).first if format
  65. result || finder.find_all(name, prefixes, partial, keys).first
  66. end
  67. end
  68. end
  69. 1 class Node
  70. 1 attr_reader :name, :logical_name, :template, :children
  71. 1 def self.create(name, logical_name, template, partial)
  72. klass = partial ? Partial : Node
  73. klass.new(name, logical_name, template, [])
  74. end
  75. 1 def initialize(name, logical_name, template, children = [])
  76. @name = name
  77. @logical_name = logical_name
  78. @template = template
  79. @children = children
  80. end
  81. 1 def digest(finder, stack = [])
  82. ActiveSupport::Digest.hexdigest("#{template.source}-#{dependency_digest(finder, stack)}")
  83. end
  84. 1 def dependency_digest(finder, stack)
  85. children.map do |node|
  86. if stack.include?(node)
  87. false
  88. else
  89. finder.digest_cache[node.name] ||= begin
  90. stack.push node
  91. node.digest(finder, stack).tap { stack.pop }
  92. end
  93. end
  94. end.join("-")
  95. end
  96. 1 def to_dep_map
  97. children.any? ? { name => children.map(&:to_dep_map) } : name
  98. end
  99. end
  100. 1 class Partial < Node; end
  101. 1 class Missing < Node
  102. 1 def digest(finder, _ = []) "" end
  103. end
  104. 1 class Injected < Node
  105. 1 def digest(finder, _ = []) name end
  106. end
  107. 1 class NullLogger
  108. 1 def self.debug(_); end
  109. 1 def self.error(_); end
  110. end
  111. end
  112. end

target/rubygems/gems/actionview-5.2.3/lib/action_view/flows.rb

47.37% lines covered

38 relevant lines. 18 lines covered and 20 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/string/output_safety"
  3. 1 module ActionView
  4. 1 class OutputFlow #:nodoc:
  5. 1 attr_reader :content
  6. 1 def initialize
  7. 1 @content = Hash.new { |h, k| h[k] = ActiveSupport::SafeBuffer.new }
  8. end
  9. # Called by _layout_for to read stored values.
  10. 1 def get(key)
  11. 1 @content[key]
  12. end
  13. # Called by each renderer object to set the layout contents.
  14. 1 def set(key, value)
  15. 1 @content[key] = ActiveSupport::SafeBuffer.new(value)
  16. end
  17. # Called by content_for
  18. 1 def append(key, value)
  19. @content[key] << value
  20. end
  21. 1 alias_method :append!, :append
  22. end
  23. 1 class StreamingFlow < OutputFlow #:nodoc:
  24. 1 def initialize(view, fiber)
  25. @view = view
  26. @parent = nil
  27. @child = view.output_buffer
  28. @content = view.view_flow.content
  29. @fiber = fiber
  30. @root = Fiber.current.object_id
  31. end
  32. # Try to get stored content. If the content
  33. # is not available and we're inside the layout fiber,
  34. # then it will begin waiting for the given key and yield.
  35. 1 def get(key)
  36. return super if @content.key?(key)
  37. if inside_fiber?
  38. view = @view
  39. begin
  40. @waiting_for = key
  41. view.output_buffer, @parent = @child, view.output_buffer
  42. Fiber.yield
  43. ensure
  44. @waiting_for = nil
  45. view.output_buffer, @child = @parent, view.output_buffer
  46. end
  47. end
  48. super
  49. end
  50. # Appends the contents for the given key. This is called
  51. # by providing and resuming back to the fiber,
  52. # if that's the key it's waiting for.
  53. 1 def append!(key, value)
  54. super
  55. @fiber.resume if @waiting_for == key
  56. end
  57. 1 private
  58. 1 def inside_fiber?
  59. Fiber.current.object_id != @root
  60. end
  61. end
  62. end

target/rubygems/gems/actionview-5.2.3/lib/action_view/renderer/abstract_renderer.rb

95.0% lines covered

20 relevant lines. 19 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActionView
  3. # This class defines the interface for a renderer. Each class that
  4. # subclasses +AbstractRenderer+ is used by the base +Renderer+ class to
  5. # render a specific type of object.
  6. #
  7. # The base +Renderer+ class uses its +render+ method to delegate to the
  8. # renderers. These currently consist of
  9. #
  10. # PartialRenderer - Used for rendering partials
  11. # TemplateRenderer - Used for rendering other types of templates
  12. # StreamingTemplateRenderer - Used for streaming
  13. #
  14. # Whenever the +render+ method is called on the base +Renderer+ class, a new
  15. # renderer object of the correct type is created, and the +render+ method on
  16. # that new object is called in turn. This abstracts the setup and rendering
  17. # into a separate classes for partials and templates.
  18. 1 class AbstractRenderer #:nodoc:
  19. 1 delegate :find_template, :find_file, :template_exists?, :any_templates?, :with_fallbacks, :with_layout_format, :formats, to: :@lookup_context
  20. 1 def initialize(lookup_context)
  21. 4 @lookup_context = lookup_context
  22. end
  23. 1 def render
  24. raise NotImplementedError
  25. end
  26. 1 private
  27. 1 def extract_details(options) # :doc:
  28. 4 @lookup_context.registered_details.each_with_object({}) do |key, details|
  29. 16 value = options[key]
  30. 16 details[key] = Array(value) if value
  31. end
  32. end
  33. 1 def instrument(name, **options) # :doc:
  34. 4 options[:identifier] ||= (@template && @template.identifier) || @path
  35. 4 ActiveSupport::Notifications.instrument("render_#{name}.action_view", options) do |payload|
  36. 4 yield payload
  37. end
  38. end
  39. 1 def prepend_formats(formats) # :doc:
  40. 4 formats = Array(formats)
  41. 4 return if formats.empty? || @lookup_context.html_fallback_for_js
  42. 1 @lookup_context.formats = formats | @lookup_context.formats
  43. end
  44. end
  45. end

target/rubygems/gems/actionview-5.2.3/lib/action_view/renderer/partial_renderer.rb

50.31% lines covered

163 relevant lines. 82 lines covered and 81 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "concurrent/map"
  3. 1 require "action_view/renderer/partial_renderer/collection_caching"
  4. 1 module ActionView
  5. 1 class PartialIteration
  6. # The number of iterations that will be done by the partial.
  7. 1 attr_reader :size
  8. # The current iteration of the partial.
  9. 1 attr_reader :index
  10. 1 def initialize(size)
  11. @size = size
  12. @index = 0
  13. end
  14. # Check if this is the first iteration of the partial.
  15. 1 def first?
  16. index == 0
  17. end
  18. # Check if this is the last iteration of the partial.
  19. 1 def last?
  20. index == size - 1
  21. end
  22. 1 def iterate! # :nodoc:
  23. @index += 1
  24. end
  25. end
  26. # = Action View Partials
  27. #
  28. # There's also a convenience method for rendering sub templates within the current controller that depends on a
  29. # single object (we call this kind of sub templates for partials). It relies on the fact that partials should
  30. # follow the naming convention of being prefixed with an underscore -- as to separate them from regular
  31. # templates that could be rendered on their own.
  32. #
  33. # In a template for Advertiser#account:
  34. #
  35. # <%= render partial: "account" %>
  36. #
  37. # This would render "advertiser/_account.html.erb".
  38. #
  39. # In another template for Advertiser#buy, we could have:
  40. #
  41. # <%= render partial: "account", locals: { account: @buyer } %>
  42. #
  43. # <% @advertisements.each do |ad| %>
  44. # <%= render partial: "ad", locals: { ad: ad } %>
  45. # <% end %>
  46. #
  47. # This would first render <tt>advertiser/_account.html.erb</tt> with <tt>@buyer</tt> passed in as the local variable +account+, then
  48. # render <tt>advertiser/_ad.html.erb</tt> and pass the local variable +ad+ to the template for display.
  49. #
  50. # == The :as and :object options
  51. #
  52. # By default ActionView::PartialRenderer doesn't have any local variables.
  53. # The <tt>:object</tt> option can be used to pass an object to the partial. For instance:
  54. #
  55. # <%= render partial: "account", object: @buyer %>
  56. #
  57. # would provide the <tt>@buyer</tt> object to the partial, available under the local variable +account+ and is
  58. # equivalent to:
  59. #
  60. # <%= render partial: "account", locals: { account: @buyer } %>
  61. #
  62. # With the <tt>:as</tt> option we can specify a different name for said local variable. For example, if we
  63. # wanted it to be +user+ instead of +account+ we'd do:
  64. #
  65. # <%= render partial: "account", object: @buyer, as: 'user' %>
  66. #
  67. # This is equivalent to
  68. #
  69. # <%= render partial: "account", locals: { user: @buyer } %>
  70. #
  71. # == \Rendering a collection of partials
  72. #
  73. # The example of partial use describes a familiar pattern where a template needs to iterate over an array and
  74. # render a sub template for each of the elements. This pattern has been implemented as a single method that
  75. # accepts an array and renders a partial by the same name as the elements contained within. So the three-lined
  76. # example in "Using partials" can be rewritten with a single line:
  77. #
  78. # <%= render partial: "ad", collection: @advertisements %>
  79. #
  80. # This will render <tt>advertiser/_ad.html.erb</tt> and pass the local variable +ad+ to the template for display. An
  81. # iteration object will automatically be made available to the template with a name of the form
  82. # +partial_name_iteration+. The iteration object has knowledge about which index the current object has in
  83. # the collection and the total size of the collection. The iteration object also has two convenience methods,
  84. # +first?+ and +last?+. In the case of the example above, the template would be fed +ad_iteration+.
  85. # For backwards compatibility the +partial_name_counter+ is still present and is mapped to the iteration's
  86. # +index+ method.
  87. #
  88. # The <tt>:as</tt> option may be used when rendering partials.
  89. #
  90. # You can specify a partial to be rendered between elements via the <tt>:spacer_template</tt> option.
  91. # The following example will render <tt>advertiser/_ad_divider.html.erb</tt> between each ad partial:
  92. #
  93. # <%= render partial: "ad", collection: @advertisements, spacer_template: "ad_divider" %>
  94. #
  95. # If the given <tt>:collection</tt> is +nil+ or empty, <tt>render</tt> will return +nil+. This will allow you
  96. # to specify a text which will be displayed instead by using this form:
  97. #
  98. # <%= render(partial: "ad", collection: @advertisements) || "There's no ad to be displayed" %>
  99. #
  100. # NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also
  101. # just keep domain objects, like Active Records, in there.
  102. #
  103. # == \Rendering shared partials
  104. #
  105. # Two controllers can share a set of partials and render them like this:
  106. #
  107. # <%= render partial: "advertisement/ad", locals: { ad: @advertisement } %>
  108. #
  109. # This will render the partial <tt>advertisement/_ad.html.erb</tt> regardless of which controller this is being called from.
  110. #
  111. # == \Rendering objects that respond to +to_partial_path+
  112. #
  113. # Instead of explicitly naming the location of a partial, you can also let PartialRenderer do the work
  114. # and pick the proper path by checking +to_partial_path+ method.
  115. #
  116. # # @account.to_partial_path returns 'accounts/account', so it can be used to replace:
  117. # # <%= render partial: "accounts/account", locals: { account: @account} %>
  118. # <%= render partial: @account %>
  119. #
  120. # # @posts is an array of Post instances, so every post record returns 'posts/post' on +to_partial_path+,
  121. # # that's why we can replace:
  122. # # <%= render partial: "posts/post", collection: @posts %>
  123. # <%= render partial: @posts %>
  124. #
  125. # == \Rendering the default case
  126. #
  127. # If you're not going to be using any of the options like collections or layouts, you can also use the short-hand
  128. # defaults of render to render partials. Examples:
  129. #
  130. # # Instead of <%= render partial: "account" %>
  131. # <%= render "account" %>
  132. #
  133. # # Instead of <%= render partial: "account", locals: { account: @buyer } %>
  134. # <%= render "account", account: @buyer %>
  135. #
  136. # # @account.to_partial_path returns 'accounts/account', so it can be used to replace:
  137. # # <%= render partial: "accounts/account", locals: { account: @account} %>
  138. # <%= render @account %>
  139. #
  140. # # @posts is an array of Post instances, so every post record returns 'posts/post' on +to_partial_path+,
  141. # # that's why we can replace:
  142. # # <%= render partial: "posts/post", collection: @posts %>
  143. # <%= render @posts %>
  144. #
  145. # == \Rendering partials with layouts
  146. #
  147. # Partials can have their own layouts applied to them. These layouts are different than the ones that are
  148. # specified globally for the entire action, but they work in a similar fashion. Imagine a list with two types
  149. # of users:
  150. #
  151. # <%# app/views/users/index.html.erb %>
  152. # Here's the administrator:
  153. # <%= render partial: "user", layout: "administrator", locals: { user: administrator } %>
  154. #
  155. # Here's the editor:
  156. # <%= render partial: "user", layout: "editor", locals: { user: editor } %>
  157. #
  158. # <%# app/views/users/_user.html.erb %>
  159. # Name: <%= user.name %>
  160. #
  161. # <%# app/views/users/_administrator.html.erb %>
  162. # <div id="administrator">
  163. # Budget: $<%= user.budget %>
  164. # <%= yield %>
  165. # </div>
  166. #
  167. # <%# app/views/users/_editor.html.erb %>
  168. # <div id="editor">
  169. # Deadline: <%= user.deadline %>
  170. # <%= yield %>
  171. # </div>
  172. #
  173. # ...this will return:
  174. #
  175. # Here's the administrator:
  176. # <div id="administrator">
  177. # Budget: $<%= user.budget %>
  178. # Name: <%= user.name %>
  179. # </div>
  180. #
  181. # Here's the editor:
  182. # <div id="editor">
  183. # Deadline: <%= user.deadline %>
  184. # Name: <%= user.name %>
  185. # </div>
  186. #
  187. # If a collection is given, the layout will be rendered once for each item in
  188. # the collection. For example, these two snippets have the same output:
  189. #
  190. # <%# app/views/users/_user.html.erb %>
  191. # Name: <%= user.name %>
  192. #
  193. # <%# app/views/users/index.html.erb %>
  194. # <%# This does not use layouts %>
  195. # <ul>
  196. # <% users.each do |user| -%>
  197. # <li>
  198. # <%= render partial: "user", locals: { user: user } %>
  199. # </li>
  200. # <% end -%>
  201. # </ul>
  202. #
  203. # <%# app/views/users/_li_layout.html.erb %>
  204. # <li>
  205. # <%= yield %>
  206. # </li>
  207. #
  208. # <%# app/views/users/index.html.erb %>
  209. # <ul>
  210. # <%= render partial: "user", layout: "li_layout", collection: users %>
  211. # </ul>
  212. #
  213. # Given two users whose names are Alice and Bob, these snippets return:
  214. #
  215. # <ul>
  216. # <li>
  217. # Name: Alice
  218. # </li>
  219. # <li>
  220. # Name: Bob
  221. # </li>
  222. # </ul>
  223. #
  224. # The current object being rendered, as well as the object_counter, will be
  225. # available as local variables inside the layout template under the same names
  226. # as available in the partial.
  227. #
  228. # You can also apply a layout to a block within any template:
  229. #
  230. # <%# app/views/users/_chief.html.erb %>
  231. # <%= render(layout: "administrator", locals: { user: chief }) do %>
  232. # Title: <%= chief.title %>
  233. # <% end %>
  234. #
  235. # ...this will return:
  236. #
  237. # <div id="administrator">
  238. # Budget: $<%= user.budget %>
  239. # Title: <%= chief.name %>
  240. # </div>
  241. #
  242. # As you can see, the <tt>:locals</tt> hash is shared between both the partial and its layout.
  243. #
  244. # If you pass arguments to "yield" then this will be passed to the block. One way to use this is to pass
  245. # an array to layout and treat it as an enumerable.
  246. #
  247. # <%# app/views/users/_user.html.erb %>
  248. # <div class="user">
  249. # Budget: $<%= user.budget %>
  250. # <%= yield user %>
  251. # </div>
  252. #
  253. # <%# app/views/users/index.html.erb %>
  254. # <%= render layout: @users do |user| %>
  255. # Title: <%= user.title %>
  256. # <% end %>
  257. #
  258. # This will render the layout for each user and yield to the block, passing the user, each time.
  259. #
  260. # You can also yield multiple times in one layout and use block arguments to differentiate the sections.
  261. #
  262. # <%# app/views/users/_user.html.erb %>
  263. # <div class="user">
  264. # <%= yield user, :header %>
  265. # Budget: $<%= user.budget %>
  266. # <%= yield user, :footer %>
  267. # </div>
  268. #
  269. # <%# app/views/users/index.html.erb %>
  270. # <%= render layout: @users do |user, section| %>
  271. # <%- case section when :header -%>
  272. # Title: <%= user.title %>
  273. # <%- when :footer -%>
  274. # Deadline: <%= user.deadline %>
  275. # <%- end -%>
  276. # <% end %>
  277. 1 class PartialRenderer < AbstractRenderer
  278. 1 include CollectionCaching
  279. 1 PREFIXED_PARTIAL_NAMES = Concurrent::Map.new do |h, k|
  280. h[k] = Concurrent::Map.new
  281. end
  282. 1 def initialize(*)
  283. 3 super
  284. 3 @context_prefix = @lookup_context.prefixes.first
  285. end
  286. 1 def render(context, options, block)
  287. 3 setup(context, options, block)
  288. 3 @template = find_partial
  289. 3 @lookup_context.rendered_format ||= begin
  290. if @template && @template.formats.present?
  291. @template.formats.first
  292. else
  293. formats.first
  294. end
  295. end
  296. 3 if @collection
  297. render_collection
  298. else
  299. 3 render_partial
  300. end
  301. end
  302. 1 private
  303. 1 def render_collection
  304. instrument(:collection, count: @collection.size) do |payload|
  305. return nil if @collection.blank?
  306. if @options.key?(:spacer_template)
  307. spacer = find_template(@options[:spacer_template], @locals.keys).render(@view, @locals)
  308. end
  309. cache_collection_render(payload) do
  310. @template ? collection_with_template : collection_without_template
  311. end.join(spacer).html_safe
  312. end
  313. end
  314. 1 def render_partial
  315. 3 instrument(:partial) do |payload|
  316. 3 view, locals, block = @view, @locals, @block
  317. 3 object, as = @object, @variable
  318. 3 if !block && (layout = @options[:layout])
  319. layout = find_template(layout.to_s, @template_keys)
  320. end
  321. 3 object = locals[as] if object.nil? # Respect object when object is false
  322. 3 locals[as] = object if @has_object
  323. 3 content = @template.render(view, locals) do |*name|
  324. view._layout_for(*name, &block)
  325. end
  326. 3 content = layout.render(view, locals) { content } if layout
  327. 3 payload[:cache_hit] = view.view_renderer.cache_hits[@template.virtual_path]
  328. 3 content
  329. end
  330. end
  331. # Sets up instance variables needed for rendering a partial. This method
  332. # finds the options and details and extracts them. The method also contains
  333. # logic that handles the type of object passed in as the partial.
  334. #
  335. # If +options[:partial]+ is a string, then the <tt>@path</tt> instance variable is
  336. # set to that string. Otherwise, the +options[:partial]+ object must
  337. # respond to +to_partial_path+ in order to setup the path.
  338. 1 def setup(context, options, block)
  339. 3 @view = context
  340. 3 @options = options
  341. 3 @block = block
  342. 3 @locals = options[:locals] || {}
  343. 3 @details = extract_details(options)
  344. 3 prepend_formats(options[:formats])
  345. 3 partial = options[:partial]
  346. 3 if String === partial
  347. 3 @has_object = options.key?(:object)
  348. 3 @object = options[:object]
  349. 3 @collection = collection_from_options
  350. 3 @path = partial
  351. else
  352. @has_object = true
  353. @object = partial
  354. @collection = collection_from_object || collection_from_options
  355. if @collection
  356. paths = @collection_data = @collection.map { |o| partial_path(o) }
  357. @path = paths.uniq.one? ? paths.first : nil
  358. else
  359. @path = partial_path
  360. end
  361. end
  362. 3 if as = options[:as]
  363. raise_invalid_option_as(as) unless /\A[a-z_]\w*\z/.match?(as.to_s)
  364. as = as.to_sym
  365. end
  366. 3 if @path
  367. 3 @variable, @variable_counter, @variable_iteration = retrieve_variable(@path, as)
  368. 3 @template_keys = retrieve_template_keys
  369. else
  370. paths.map! { |path| retrieve_variable(path, as).unshift(path) }
  371. end
  372. 3 self
  373. end
  374. 1 def collection_from_options
  375. 3 if @options.key?(:collection)
  376. collection = @options[:collection]
  377. collection ? collection.to_a : []
  378. end
  379. end
  380. 1 def collection_from_object
  381. @object.to_ary if @object.respond_to?(:to_ary)
  382. end
  383. 1 def find_partial
  384. 3 find_template(@path, @template_keys) if @path
  385. end
  386. 1 def find_template(path, locals)
  387. 3 prefixes = path.include?(?/) ? [] : @lookup_context.prefixes
  388. 3 @lookup_context.find_template(path, prefixes, true, locals, @details)
  389. end
  390. 1 def collection_with_template
  391. view, locals, template = @view, @locals, @template
  392. as, counter, iteration = @variable, @variable_counter, @variable_iteration
  393. if layout = @options[:layout]
  394. layout = find_template(layout, @template_keys)
  395. end
  396. partial_iteration = PartialIteration.new(@collection.size)
  397. locals[iteration] = partial_iteration
  398. @collection.map do |object|
  399. locals[as] = object
  400. locals[counter] = partial_iteration.index
  401. content = template.render(view, locals)
  402. content = layout.render(view, locals) { content } if layout
  403. partial_iteration.iterate!
  404. content
  405. end
  406. end
  407. 1 def collection_without_template
  408. view, locals, collection_data = @view, @locals, @collection_data
  409. cache = {}
  410. keys = @locals.keys
  411. partial_iteration = PartialIteration.new(@collection.size)
  412. @collection.map do |object|
  413. index = partial_iteration.index
  414. path, as, counter, iteration = collection_data[index]
  415. locals[as] = object
  416. locals[counter] = index
  417. locals[iteration] = partial_iteration
  418. template = (cache[path] ||= find_template(path, keys + [as, counter, iteration]))
  419. content = template.render(view, locals)
  420. partial_iteration.iterate!
  421. content
  422. end
  423. end
  424. # Obtains the path to where the object's partial is located. If the object
  425. # responds to +to_partial_path+, then +to_partial_path+ will be called and
  426. # will provide the path. If the object does not respond to +to_partial_path+,
  427. # then an +ArgumentError+ is raised.
  428. #
  429. # If +prefix_partial_path_with_controller_namespace+ is true, then this
  430. # method will prefix the partial paths with a namespace.
  431. 1 def partial_path(object = @object)
  432. object = object.to_model if object.respond_to?(:to_model)
  433. path = if object.respond_to?(:to_partial_path)
  434. object.to_partial_path
  435. else
  436. raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.")
  437. end
  438. if @view.prefix_partial_path_with_controller_namespace
  439. prefixed_partial_names[path] ||= merge_prefix_into_object_path(@context_prefix, path.dup)
  440. else
  441. path
  442. end
  443. end
  444. 1 def prefixed_partial_names
  445. @prefixed_partial_names ||= PREFIXED_PARTIAL_NAMES[@context_prefix]
  446. end
  447. 1 def merge_prefix_into_object_path(prefix, object_path)
  448. if prefix.include?(?/) && object_path.include?(?/)
  449. prefixes = []
  450. prefix_array = File.dirname(prefix).split("/")
  451. object_path_array = object_path.split("/")[0..-3] # skip model dir & partial
  452. prefix_array.each_with_index do |dir, index|
  453. break if dir == object_path_array[index]
  454. prefixes << dir
  455. end
  456. (prefixes << object_path).join("/")
  457. else
  458. object_path
  459. end
  460. end
  461. 1 def retrieve_template_keys
  462. 3 keys = @locals.keys
  463. 3 keys << @variable if @has_object || @collection
  464. 3 if @collection
  465. keys << @variable_counter
  466. keys << @variable_iteration
  467. end
  468. 3 keys
  469. end
  470. 1 def retrieve_variable(path, as)
  471. 3 variable = as || begin
  472. 3 base = path[-1] == "/".freeze ? "".freeze : File.basename(path)
  473. 3 raise_invalid_identifier(path) unless base =~ /\A_?(.*?)(?:\.\w+)*\z/
  474. 3 $1.to_sym
  475. end
  476. 3 if @collection
  477. variable_counter = :"#{variable}_counter"
  478. variable_iteration = :"#{variable}_iteration"
  479. end
  480. 3 [variable, variable_counter, variable_iteration]
  481. end
  482. 1 IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " \
  483. "make sure your partial name starts with underscore."
  484. 1 OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " \
  485. "make sure it starts with lowercase letter, " \
  486. "and is followed by any combination of letters, numbers and underscores."
  487. 1 def raise_invalid_identifier(path)
  488. raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % (path))
  489. end
  490. 1 def raise_invalid_option_as(as)
  491. raise ArgumentError.new(OPTION_AS_ERROR_MESSAGE % (as))
  492. end
  493. end
  494. end

target/rubygems/gems/actionview-5.2.3/lib/action_view/renderer/partial_renderer/collection_caching.rb

36.67% lines covered

30 relevant lines. 11 lines covered and 19 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActionView
  3. 1 module CollectionCaching # :nodoc:
  4. 1 extend ActiveSupport::Concern
  5. 1 included do
  6. # Fallback cache store if Action View is used without Rails.
  7. # Otherwise overridden in Railtie to use Rails.cache.
  8. 1 mattr_accessor :collection_cache, default: ActiveSupport::Cache::MemoryStore.new
  9. end
  10. 1 private
  11. 1 def cache_collection_render(instrumentation_payload)
  12. return yield unless @options[:cached]
  13. keyed_collection = collection_by_cache_keys
  14. cached_partials = collection_cache.read_multi(*keyed_collection.keys)
  15. instrumentation_payload[:cache_hits] = cached_partials.size
  16. @collection = keyed_collection.reject { |key, _| cached_partials.key?(key) }.values
  17. rendered_partials = @collection.empty? ? [] : yield
  18. index = 0
  19. fetch_or_cache_partial(cached_partials, order_by: keyed_collection.each_key) do
  20. rendered_partials[index].tap { index += 1 }
  21. end
  22. end
  23. 1 def callable_cache_key?
  24. @options[:cached].respond_to?(:call)
  25. end
  26. 1 def collection_by_cache_keys
  27. seed = callable_cache_key? ? @options[:cached] : ->(i) { i }
  28. @collection.each_with_object({}) do |item, hash|
  29. hash[expanded_cache_key(seed.call(item))] = item
  30. end
  31. end
  32. 1 def expanded_cache_key(key)
  33. key = @view.combined_fragment_cache_key(@view.cache_fragment_name(key, virtual_path: @template.virtual_path))
  34. key.frozen? ? key.dup : key # #read_multi & #write may require mutability, Dalli 2.6.0.
  35. end
  36. 1 def fetch_or_cache_partial(cached_partials, order_by:)
  37. order_by.map do |cache_key|
  38. cached_partials.fetch(cache_key) do
  39. yield.tap do |rendered_partial|
  40. collection_cache.write(cache_key, rendered_partial)
  41. end
  42. end
  43. end
  44. end
  45. end
  46. end

target/rubygems/gems/actionview-5.2.3/lib/action_view/renderer/renderer.rb

78.95% lines covered

19 relevant lines. 15 lines covered and 4 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActionView
  3. # This is the main entry point for rendering. It basically delegates
  4. # to other objects like TemplateRenderer and PartialRenderer which
  5. # actually renders the template.
  6. #
  7. # The Renderer will parse the options from the +render+ or +render_body+
  8. # method and render a partial or a template based on the options. The
  9. # +TemplateRenderer+ and +PartialRenderer+ objects are wrappers which do all
  10. # the setup and logic necessary to render a view and a new object is created
  11. # each time +render+ is called.
  12. 1 class Renderer
  13. 1 attr_accessor :lookup_context
  14. 1 def initialize(lookup_context)
  15. 1 @lookup_context = lookup_context
  16. end
  17. # Main render entry point shared by Action View and Action Controller.
  18. 1 def render(context, options)
  19. 1 if options.key?(:partial)
  20. render_partial(context, options)
  21. else
  22. 1 render_template(context, options)
  23. end
  24. end
  25. # Render but returns a valid Rack body. If fibers are defined, we return
  26. # a streaming body that renders the template piece by piece.
  27. #
  28. # Note that partials are not supported to be rendered with streaming,
  29. # so in such cases, we just wrap them in an array.
  30. 1 def render_body(context, options)
  31. if options.key?(:partial)
  32. [render_partial(context, options)]
  33. else
  34. StreamingTemplateRenderer.new(@lookup_context).render(context, options)
  35. end
  36. end
  37. # Direct access to template rendering.
  38. 1 def render_template(context, options) #:nodoc:
  39. 1 TemplateRenderer.new(@lookup_context).render(context, options)
  40. end
  41. # Direct access to partial rendering.
  42. 1 def render_partial(context, options, &block) #:nodoc:
  43. 3 PartialRenderer.new(@lookup_context).render(context, options, block)
  44. end
  45. 1 def cache_hits # :nodoc:
  46. 3 @cache_hits ||= {}
  47. end
  48. end
  49. end

target/rubygems/gems/actionview-5.2.3/lib/action_view/renderer/template_renderer.rb

64.29% lines covered

56 relevant lines. 36 lines covered and 20 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/object/try"
  3. 1 module ActionView
  4. 1 class TemplateRenderer < AbstractRenderer #:nodoc:
  5. 1 def render(context, options)
  6. 1 @view = context
  7. 1 @details = extract_details(options)
  8. 1 template = determine_template(options)
  9. 1 prepend_formats(template.formats)
  10. 1 @lookup_context.rendered_format ||= (template.formats.first || formats.first)
  11. 1 render_template(template, options[:layout], options[:locals])
  12. end
  13. 1 private
  14. # Determine the template to be rendered using the given options.
  15. 1 def determine_template(options)
  16. 1 keys = options.has_key?(:locals) ? options[:locals].keys : []
  17. 1 if options.key?(:body)
  18. Template::Text.new(options[:body])
  19. elsif options.key?(:plain)
  20. Template::Text.new(options[:plain])
  21. elsif options.key?(:html)
  22. Template::HTML.new(options[:html], formats.first)
  23. elsif options.key?(:file)
  24. with_fallbacks { find_file(options[:file], nil, false, keys, @details) }
  25. elsif options.key?(:inline)
  26. handler = Template.handler_for_extension(options[:type] || "erb")
  27. Template.new(options[:inline], "inline template", handler, locals: keys)
  28. elsif options.key?(:template)
  29. 1 if options[:template].respond_to?(:render)
  30. options[:template]
  31. else
  32. 1 find_template(options[:template], options[:prefixes], false, keys, @details)
  33. end
  34. else
  35. raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :html or :body option."
  36. end
  37. end
  38. # Renders the given template. A string representing the layout can be
  39. # supplied as well.
  40. 1 def render_template(template, layout_name = nil, locals = nil)
  41. 1 view, locals = @view, locals || {}
  42. 1 render_with_layout(layout_name, locals) do |layout|
  43. 1 instrument(:template, identifier: template.identifier, layout: layout.try(:virtual_path)) do
  44. 1 template.render(view, locals) { |*name| view._layout_for(*name) }
  45. end
  46. end
  47. end
  48. 1 def render_with_layout(path, locals)
  49. 1 layout = path && find_layout(path, locals.keys, [formats.first])
  50. 1 content = yield(layout)
  51. 1 if layout
  52. 1 view = @view
  53. 1 view.view_flow.set(:layout, content)
  54. 2 layout.render(view, locals) { |*name| view._layout_for(*name) }
  55. else
  56. content
  57. end
  58. end
  59. # This is the method which actually finds the layout using details in the lookup
  60. # context object. If no layout is found, it checks if at least a layout with
  61. # the given name exists across all details before raising the error.
  62. 1 def find_layout(layout, keys, formats)
  63. 1 resolve_layout(layout, keys, formats)
  64. end
  65. 1 def resolve_layout(layout, keys, formats)
  66. 2 details = @details.dup
  67. 2 details[:formats] = formats
  68. 2 case layout
  69. when String
  70. begin
  71. if layout.start_with?("/")
  72. with_fallbacks { find_template(layout, nil, false, [], details) }
  73. else
  74. find_template(layout, nil, false, [], details)
  75. end
  76. rescue ActionView::MissingTemplate
  77. all_details = @details.merge(formats: @lookup_context.default_formats)
  78. raise unless template_exists?(layout, nil, false, [], all_details)
  79. end
  80. when Proc
  81. 1 resolve_layout(layout.call(formats), keys, formats)
  82. else
  83. 1 layout
  84. end
  85. end
  86. end
  87. end

target/rubygems/gems/actionview-5.2.3/lib/action_view/routing_url_for.rb

39.47% lines covered

38 relevant lines. 15 lines covered and 23 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "action_dispatch/routing/polymorphic_routes"
  3. 1 module ActionView
  4. 1 module RoutingUrlFor
  5. # Returns the URL for the set of +options+ provided. This takes the
  6. # same options as +url_for+ in Action Controller (see the
  7. # documentation for <tt>ActionController::Base#url_for</tt>). Note that by default
  8. # <tt>:only_path</tt> is <tt>true</tt> so you'll get the relative "/controller/action"
  9. # instead of the fully qualified URL like "http://example.com/controller/action".
  10. #
  11. # ==== Options
  12. # * <tt>:anchor</tt> - Specifies the anchor name to be appended to the path.
  13. # * <tt>:only_path</tt> - If true, returns the relative URL (omitting the protocol, host name, and port) (<tt>true</tt> by default unless <tt>:host</tt> is specified).
  14. # * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2005/". Note that this
  15. # is currently not recommended since it breaks caching.
  16. # * <tt>:host</tt> - Overrides the default (current) host if provided.
  17. # * <tt>:protocol</tt> - Overrides the default (current) protocol if provided.
  18. # * <tt>:user</tt> - Inline HTTP authentication (only plucked out if <tt>:password</tt> is also present).
  19. # * <tt>:password</tt> - Inline HTTP authentication (only plucked out if <tt>:user</tt> is also present).
  20. #
  21. # ==== Relying on named routes
  22. #
  23. # Passing a record (like an Active Record) instead of a hash as the options parameter will
  24. # trigger the named route for that record. The lookup will happen on the name of the class. So passing a
  25. # Workshop object will attempt to use the +workshop_path+ route. If you have a nested route, such as
  26. # +admin_workshop_path+ you'll have to call that explicitly (it's impossible for +url_for+ to guess that route).
  27. #
  28. # ==== Implicit Controller Namespacing
  29. #
  30. # Controllers passed in using the +:controller+ option will retain their namespace unless it is an absolute one.
  31. #
  32. # ==== Examples
  33. # <%= url_for(action: 'index') %>
  34. # # => /blogs/
  35. #
  36. # <%= url_for(action: 'find', controller: 'books') %>
  37. # # => /books/find
  38. #
  39. # <%= url_for(action: 'login', controller: 'members', only_path: false, protocol: 'https') %>
  40. # # => https://www.example.com/members/login/
  41. #
  42. # <%= url_for(action: 'play', anchor: 'player') %>
  43. # # => /messages/play/#player
  44. #
  45. # <%= url_for(action: 'jump', anchor: 'tax&ship') %>
  46. # # => /testing/jump/#tax&ship
  47. #
  48. # <%= url_for(Workshop.new) %>
  49. # # relies on Workshop answering a persisted? call (and in this case returning false)
  50. # # => /workshops
  51. #
  52. # <%= url_for(@workshop) %>
  53. # # calls @workshop.to_param which by default returns the id
  54. # # => /workshops/5
  55. #
  56. # # to_param can be re-defined in a model to provide different URL names:
  57. # # => /workshops/1-workshop-name
  58. #
  59. # <%= url_for("http://www.example.com") %>
  60. # # => http://www.example.com
  61. #
  62. # <%= url_for(:back) %>
  63. # # if request.env["HTTP_REFERER"] is set to "http://www.example.com"
  64. # # => http://www.example.com
  65. #
  66. # <%= url_for(:back) %>
  67. # # if request.env["HTTP_REFERER"] is not set or is blank
  68. # # => javascript:history.back()
  69. #
  70. # <%= url_for(action: 'index', controller: 'users') %>
  71. # # Assuming an "admin" namespace
  72. # # => /admin/users
  73. #
  74. # <%= url_for(action: 'index', controller: '/users') %>
  75. # # Specify absolute path with beginning slash
  76. # # => /users
  77. 1 def url_for(options = nil)
  78. 1 case options
  79. when String
  80. 1 options
  81. when nil
  82. super(only_path: _generate_paths_by_default)
  83. when Hash
  84. options = options.symbolize_keys
  85. unless options.key?(:only_path)
  86. options[:only_path] = only_path?(options[:host])
  87. end
  88. super(options)
  89. when ActionController::Parameters
  90. unless options.key?(:only_path)
  91. options[:only_path] = only_path?(options[:host])
  92. end
  93. super(options)
  94. when :back
  95. _back_url
  96. when Array
  97. components = options.dup
  98. if _generate_paths_by_default
  99. polymorphic_path(components, components.extract_options!)
  100. else
  101. polymorphic_url(components, components.extract_options!)
  102. end
  103. else
  104. method = _generate_paths_by_default ? :path : :url
  105. builder = ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.send(method)
  106. case options
  107. when Symbol
  108. builder.handle_string_call(self, options)
  109. when Class
  110. builder.handle_class_call(self, options)
  111. else
  112. builder.handle_model_call(self, options)
  113. end
  114. end
  115. end
  116. 1 def url_options #:nodoc:
  117. 1 return super unless controller.respond_to?(:url_options)
  118. 1 controller.url_options
  119. end
  120. 1 private
  121. 1 def _routes_context
  122. controller
  123. end
  124. 1 def optimize_routes_generation?
  125. 1 controller.respond_to?(:optimize_routes_generation?, true) ?
  126. controller.optimize_routes_generation? : super
  127. end
  128. 1 def _generate_paths_by_default
  129. true
  130. end
  131. 1 def only_path?(host)
  132. _generate_paths_by_default unless host
  133. end
  134. end
  135. end

target/rubygems/gems/activejob-5.2.3/lib/active_job/arguments.rb

35.9% lines covered

78 relevant lines. 28 lines covered and 50 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/hash"
  3. 1 module ActiveJob
  4. # Raised when an exception is raised during job arguments deserialization.
  5. #
  6. # Wraps the original exception raised as +cause+.
  7. 1 class DeserializationError < StandardError
  8. 1 def initialize #:nodoc:
  9. super("Error while trying to deserialize arguments: #{$!.message}")
  10. set_backtrace $!.backtrace
  11. end
  12. end
  13. # Raised when an unsupported argument type is set as a job argument. We
  14. # currently support NilClass, Integer, Fixnum, Float, String, TrueClass, FalseClass,
  15. # Bignum, BigDecimal, and objects that can be represented as GlobalIDs (ex: Active Record).
  16. # Raised if you set the key for a Hash something else than a string or
  17. # a symbol. Also raised when trying to serialize an object which can't be
  18. # identified with a Global ID - such as an unpersisted Active Record model.
  19. 1 class SerializationError < ArgumentError; end
  20. 1 module Arguments
  21. 1 extend self
  22. # :nodoc:
  23. 1 TYPE_WHITELIST = [ NilClass, String, Integer, Float, BigDecimal, TrueClass, FalseClass ]
  24. 1 TYPE_WHITELIST.push(Fixnum, Bignum) unless 1.class == Integer
  25. # Serializes a set of arguments. Whitelisted types are returned
  26. # as-is. Arrays/Hashes are serialized element by element.
  27. # All other types are serialized using GlobalID.
  28. 1 def serialize(arguments)
  29. arguments.map { |argument| serialize_argument(argument) }
  30. end
  31. # Deserializes a set of arguments. Whitelisted types are returned
  32. # as-is. Arrays/Hashes are deserialized element by element.
  33. # All other types are deserialized using GlobalID.
  34. 1 def deserialize(arguments)
  35. arguments.map { |argument| deserialize_argument(argument) }
  36. rescue
  37. raise DeserializationError
  38. end
  39. 1 private
  40. # :nodoc:
  41. 1 GLOBALID_KEY = "_aj_globalid".freeze
  42. # :nodoc:
  43. 1 SYMBOL_KEYS_KEY = "_aj_symbol_keys".freeze
  44. # :nodoc:
  45. 1 WITH_INDIFFERENT_ACCESS_KEY = "_aj_hash_with_indifferent_access".freeze
  46. 1 private_constant :GLOBALID_KEY, :SYMBOL_KEYS_KEY, :WITH_INDIFFERENT_ACCESS_KEY
  47. 1 def serialize_argument(argument)
  48. case argument
  49. when *TYPE_WHITELIST
  50. argument
  51. when GlobalID::Identification
  52. convert_to_global_id_hash(argument)
  53. when Array
  54. argument.map { |arg| serialize_argument(arg) }
  55. when ActiveSupport::HashWithIndifferentAccess
  56. serialize_indifferent_hash(argument)
  57. when Hash
  58. symbol_keys = argument.each_key.grep(Symbol).map(&:to_s)
  59. result = serialize_hash(argument)
  60. result[SYMBOL_KEYS_KEY] = symbol_keys
  61. result
  62. when -> (arg) { arg.respond_to?(:permitted?) }
  63. serialize_indifferent_hash(argument.to_h)
  64. else
  65. raise SerializationError.new("Unsupported argument type: #{argument.class.name}")
  66. end
  67. end
  68. 1 def deserialize_argument(argument)
  69. case argument
  70. when String
  71. argument
  72. when *TYPE_WHITELIST
  73. argument
  74. when Array
  75. argument.map { |arg| deserialize_argument(arg) }
  76. when Hash
  77. if serialized_global_id?(argument)
  78. deserialize_global_id argument
  79. else
  80. deserialize_hash(argument)
  81. end
  82. else
  83. raise ArgumentError, "Can only deserialize primitive arguments: #{argument.inspect}"
  84. end
  85. end
  86. 1 def serialized_global_id?(hash)
  87. hash.size == 1 && hash.include?(GLOBALID_KEY)
  88. end
  89. 1 def deserialize_global_id(hash)
  90. GlobalID::Locator.locate hash[GLOBALID_KEY]
  91. end
  92. 1 def serialize_hash(argument)
  93. argument.each_with_object({}) do |(key, value), hash|
  94. hash[serialize_hash_key(key)] = serialize_argument(value)
  95. end
  96. end
  97. 1 def deserialize_hash(serialized_hash)
  98. result = serialized_hash.transform_values { |v| deserialize_argument(v) }
  99. if result.delete(WITH_INDIFFERENT_ACCESS_KEY)
  100. result = result.with_indifferent_access
  101. elsif symbol_keys = result.delete(SYMBOL_KEYS_KEY)
  102. result = transform_symbol_keys(result, symbol_keys)
  103. end
  104. result
  105. end
  106. # :nodoc:
  107. 1 RESERVED_KEYS = [
  108. GLOBALID_KEY, GLOBALID_KEY.to_sym,
  109. SYMBOL_KEYS_KEY, SYMBOL_KEYS_KEY.to_sym,
  110. WITH_INDIFFERENT_ACCESS_KEY, WITH_INDIFFERENT_ACCESS_KEY.to_sym,
  111. ]
  112. 1 private_constant :RESERVED_KEYS
  113. 1 def serialize_hash_key(key)
  114. case key
  115. when *RESERVED_KEYS
  116. raise SerializationError.new("Can't serialize a Hash with reserved key #{key.inspect}")
  117. when String, Symbol
  118. key.to_s
  119. else
  120. raise SerializationError.new("Only string and symbol hash keys may be serialized as job arguments, but #{key.inspect} is a #{key.class}")
  121. end
  122. end
  123. 1 def serialize_indifferent_hash(indifferent_hash)
  124. result = serialize_hash(indifferent_hash)
  125. result[WITH_INDIFFERENT_ACCESS_KEY] = serialize_argument(true)
  126. result
  127. end
  128. 1 def transform_symbol_keys(hash, symbol_keys)
  129. # NOTE: HashWithIndifferentAccess#transform_keys always
  130. # returns stringified keys with indifferent access
  131. # so we call #to_h here to ensure keys are symbolized.
  132. hash.to_h.transform_keys do |key|
  133. if symbol_keys.include?(key)
  134. key.to_sym
  135. else
  136. key
  137. end
  138. end
  139. end
  140. 1 def convert_to_global_id_hash(argument)
  141. { GLOBALID_KEY => argument.to_global_id.to_s }
  142. rescue URI::GID::MissingModelIdError
  143. raise SerializationError, "Unable to serialize #{argument.class} " \
  144. "without an id. (Maybe you forgot to call save?)"
  145. end
  146. end
  147. end

target/rubygems/gems/activejob-5.2.3/lib/active_job/base.rb

100.0% lines covered

23 relevant lines. 23 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_job/core"
  3. 1 require "active_job/queue_adapter"
  4. 1 require "active_job/queue_name"
  5. 1 require "active_job/queue_priority"
  6. 1 require "active_job/enqueuing"
  7. 1 require "active_job/execution"
  8. 1 require "active_job/callbacks"
  9. 1 require "active_job/exceptions"
  10. 1 require "active_job/logging"
  11. 1 require "active_job/translation"
  12. 1 module ActiveJob #:nodoc:
  13. # = Active Job
  14. #
  15. # Active Job objects can be configured to work with different backend
  16. # queuing frameworks. To specify a queue adapter to use:
  17. #
  18. # ActiveJob::Base.queue_adapter = :inline
  19. #
  20. # A list of supported adapters can be found in QueueAdapters.
  21. #
  22. # Active Job objects can be defined by creating a class that inherits
  23. # from the ActiveJob::Base class. The only necessary method to
  24. # implement is the "perform" method.
  25. #
  26. # To define an Active Job object:
  27. #
  28. # class ProcessPhotoJob < ActiveJob::Base
  29. # def perform(photo)
  30. # photo.watermark!('Rails')
  31. # photo.rotate!(90.degrees)
  32. # photo.resize_to_fit!(300, 300)
  33. # photo.upload!
  34. # end
  35. # end
  36. #
  37. # Records that are passed in are serialized/deserialized using Global
  38. # ID. More information can be found in Arguments.
  39. #
  40. # To enqueue a job to be performed as soon as the queueing system is free:
  41. #
  42. # ProcessPhotoJob.perform_later(photo)
  43. #
  44. # To enqueue a job to be processed at some point in the future:
  45. #
  46. # ProcessPhotoJob.set(wait_until: Date.tomorrow.noon).perform_later(photo)
  47. #
  48. # More information can be found in ActiveJob::Core::ClassMethods#set
  49. #
  50. # A job can also be processed immediately without sending to the queue:
  51. #
  52. # ProcessPhotoJob.perform_now(photo)
  53. #
  54. # == Exceptions
  55. #
  56. # * DeserializationError - Error class for deserialization errors.
  57. # * SerializationError - Error class for serialization errors.
  58. 1 class Base
  59. 1 include Core
  60. 1 include QueueAdapter
  61. 1 include QueueName
  62. 1 include QueuePriority
  63. 1 include Enqueuing
  64. 1 include Execution
  65. 1 include Callbacks
  66. 1 include Exceptions
  67. 1 include Logging
  68. 1 include Translation
  69. 1 ActiveSupport.run_load_hooks(:active_job, self)
  70. end
  71. end

target/rubygems/gems/activejob-5.2.3/lib/active_job/callbacks.rb

87.5% lines covered

24 relevant lines. 21 lines covered and 3 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/callbacks"
  3. 1 module ActiveJob
  4. # = Active Job Callbacks
  5. #
  6. # Active Job provides hooks during the life cycle of a job. Callbacks allow you
  7. # to trigger logic during this cycle. Available callbacks are:
  8. #
  9. # * <tt>before_enqueue</tt>
  10. # * <tt>around_enqueue</tt>
  11. # * <tt>after_enqueue</tt>
  12. # * <tt>before_perform</tt>
  13. # * <tt>around_perform</tt>
  14. # * <tt>after_perform</tt>
  15. #
  16. # NOTE: Calling the same callback multiple times will overwrite previous callback definitions.
  17. #
  18. 1 module Callbacks
  19. 1 extend ActiveSupport::Concern
  20. 1 include ActiveSupport::Callbacks
  21. 1 class << self
  22. 1 include ActiveSupport::Callbacks
  23. 1 define_callbacks :execute
  24. end
  25. 1 included do
  26. 1 define_callbacks :perform
  27. 1 define_callbacks :enqueue
  28. end
  29. # These methods will be included into any Active Job object, adding
  30. # callbacks for +perform+ and +enqueue+ methods.
  31. 1 module ClassMethods
  32. # Defines a callback that will get called right before the
  33. # job's perform method is executed.
  34. #
  35. # class VideoProcessJob < ActiveJob::Base
  36. # queue_as :default
  37. #
  38. # before_perform do |job|
  39. # UserMailer.notify_video_started_processing(job.arguments.first)
  40. # end
  41. #
  42. # def perform(video_id)
  43. # Video.find(video_id).process
  44. # end
  45. # end
  46. #
  47. 1 def before_perform(*filters, &blk)
  48. set_callback(:perform, :before, *filters, &blk)
  49. end
  50. # Defines a callback that will get called right after the
  51. # job's perform method has finished.
  52. #
  53. # class VideoProcessJob < ActiveJob::Base
  54. # queue_as :default
  55. #
  56. # after_perform do |job|
  57. # UserMailer.notify_video_processed(job.arguments.first)
  58. # end
  59. #
  60. # def perform(video_id)
  61. # Video.find(video_id).process
  62. # end
  63. # end
  64. #
  65. 1 def after_perform(*filters, &blk)
  66. set_callback(:perform, :after, *filters, &blk)
  67. end
  68. # Defines a callback that will get called around the job's perform method.
  69. #
  70. # class VideoProcessJob < ActiveJob::Base
  71. # queue_as :default
  72. #
  73. # around_perform do |job, block|
  74. # UserMailer.notify_video_started_processing(job.arguments.first)
  75. # block.call
  76. # UserMailer.notify_video_processed(job.arguments.first)
  77. # end
  78. #
  79. # def perform(video_id)
  80. # Video.find(video_id).process
  81. # end
  82. # end
  83. #
  84. 1 def around_perform(*filters, &blk)
  85. 2 set_callback(:perform, :around, *filters, &blk)
  86. end
  87. # Defines a callback that will get called right before the
  88. # job is enqueued.
  89. #
  90. # class VideoProcessJob < ActiveJob::Base
  91. # queue_as :default
  92. #
  93. # before_enqueue do |job|
  94. # $statsd.increment "enqueue-video-job.try"
  95. # end
  96. #
  97. # def perform(video_id)
  98. # Video.find(video_id).process
  99. # end
  100. # end
  101. #
  102. 1 def before_enqueue(*filters, &blk)
  103. set_callback(:enqueue, :before, *filters, &blk)
  104. end
  105. # Defines a callback that will get called right after the
  106. # job is enqueued.
  107. #
  108. # class VideoProcessJob < ActiveJob::Base
  109. # queue_as :default
  110. #
  111. # after_enqueue do |job|
  112. # $statsd.increment "enqueue-video-job.success"
  113. # end
  114. #
  115. # def perform(video_id)
  116. # Video.find(video_id).process
  117. # end
  118. # end
  119. #
  120. 1 def after_enqueue(*filters, &blk)
  121. 1 set_callback(:enqueue, :after, *filters, &blk)
  122. end
  123. # Defines a callback that will get called around the enqueueing
  124. # of the job.
  125. #
  126. # class VideoProcessJob < ActiveJob::Base
  127. # queue_as :default
  128. #
  129. # around_enqueue do |job, block|
  130. # $statsd.time "video-job.process" do
  131. # block.call
  132. # end
  133. # end
  134. #
  135. # def perform(video_id)
  136. # Video.find(video_id).process
  137. # end
  138. # end
  139. #
  140. 1 def around_enqueue(*filters, &blk)
  141. 1 set_callback(:enqueue, :around, *filters, &blk)
  142. end
  143. end
  144. end
  145. end

target/rubygems/gems/activejob-5.2.3/lib/active_job/core.rb

49.02% lines covered

51 relevant lines. 25 lines covered and 26 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveJob
  3. # Provides general behavior that will be included into every Active Job
  4. # object that inherits from ActiveJob::Base.
  5. 1 module Core
  6. 1 extend ActiveSupport::Concern
  7. 1 included do
  8. # Job arguments
  9. 1 attr_accessor :arguments
  10. 1 attr_writer :serialized_arguments
  11. # Timestamp when the job should be performed
  12. 1 attr_accessor :scheduled_at
  13. # Job Identifier
  14. 1 attr_accessor :job_id
  15. # Queue in which the job will reside.
  16. 1 attr_writer :queue_name
  17. # Priority that the job will have (lower is more priority).
  18. 1 attr_writer :priority
  19. # ID optionally provided by adapter
  20. 1 attr_accessor :provider_job_id
  21. # Number of times this job has been executed (which increments on every retry, like after an exception).
  22. 1 attr_accessor :executions
  23. # I18n.locale to be used during the job.
  24. 1 attr_accessor :locale
  25. end
  26. # These methods will be included into any Active Job object, adding
  27. # helpers for de/serialization and creation of job instances.
  28. 1 module ClassMethods
  29. # Creates a new job instance from a hash created with +serialize+
  30. 1 def deserialize(job_data)
  31. job = job_data["job_class"].constantize.new
  32. job.deserialize(job_data)
  33. job
  34. end
  35. # Creates a job preconfigured with the given options. You can call
  36. # perform_later with the job arguments to enqueue the job with the
  37. # preconfigured options
  38. #
  39. # ==== Options
  40. # * <tt>:wait</tt> - Enqueues the job with the specified delay
  41. # * <tt>:wait_until</tt> - Enqueues the job at the time specified
  42. # * <tt>:queue</tt> - Enqueues the job on the specified queue
  43. # * <tt>:priority</tt> - Enqueues the job with the specified priority
  44. #
  45. # ==== Examples
  46. #
  47. # VideoJob.set(queue: :some_queue).perform_later(Video.last)
  48. # VideoJob.set(wait: 5.minutes).perform_later(Video.last)
  49. # VideoJob.set(wait_until: Time.now.tomorrow).perform_later(Video.last)
  50. # VideoJob.set(queue: :some_queue, wait: 5.minutes).perform_later(Video.last)
  51. # VideoJob.set(queue: :some_queue, wait_until: Time.now.tomorrow).perform_later(Video.last)
  52. # VideoJob.set(queue: :some_queue, wait: 5.minutes, priority: 10).perform_later(Video.last)
  53. 1 def set(options = {})
  54. ConfiguredJob.new(self, options)
  55. end
  56. end
  57. # Creates a new job instance. Takes the arguments that will be
  58. # passed to the perform method.
  59. 1 def initialize(*arguments)
  60. @arguments = arguments
  61. @job_id = SecureRandom.uuid
  62. @queue_name = self.class.queue_name
  63. @priority = self.class.priority
  64. @executions = 0
  65. end
  66. # Returns a hash with the job data that can safely be passed to the
  67. # queueing adapter.
  68. 1 def serialize
  69. {
  70. "job_class" => self.class.name,
  71. "job_id" => job_id,
  72. "provider_job_id" => provider_job_id,
  73. "queue_name" => queue_name,
  74. "priority" => priority,
  75. "arguments" => serialize_arguments_if_needed(arguments),
  76. "executions" => executions,
  77. "locale" => I18n.locale.to_s
  78. }
  79. end
  80. # Attaches the stored job data to the current instance. Receives a hash
  81. # returned from +serialize+
  82. #
  83. # ==== Examples
  84. #
  85. # class DeliverWebhookJob < ActiveJob::Base
  86. # attr_writer :attempt_number
  87. #
  88. # def attempt_number
  89. # @attempt_number ||= 0
  90. # end
  91. #
  92. # def serialize
  93. # super.merge('attempt_number' => attempt_number + 1)
  94. # end
  95. #
  96. # def deserialize(job_data)
  97. # super
  98. # self.attempt_number = job_data['attempt_number']
  99. # end
  100. #
  101. # rescue_from(Timeout::Error) do |exception|
  102. # raise exception if attempt_number > 5
  103. # retry_job(wait: 10)
  104. # end
  105. # end
  106. 1 def deserialize(job_data)
  107. self.job_id = job_data["job_id"]
  108. self.provider_job_id = job_data["provider_job_id"]
  109. self.queue_name = job_data["queue_name"]
  110. self.priority = job_data["priority"]
  111. self.serialized_arguments = job_data["arguments"]
  112. self.executions = job_data["executions"]
  113. self.locale = job_data["locale"] || I18n.locale.to_s
  114. end
  115. 1 private
  116. 1 def serialize_arguments_if_needed(arguments)
  117. if arguments_serialized?
  118. @serialized_arguments
  119. else
  120. serialize_arguments(arguments)
  121. end
  122. end
  123. 1 def deserialize_arguments_if_needed
  124. if arguments_serialized?
  125. @arguments = deserialize_arguments(@serialized_arguments)
  126. @serialized_arguments = nil
  127. end
  128. end
  129. 1 def serialize_arguments(arguments)
  130. Arguments.serialize(arguments)
  131. end
  132. 1 def deserialize_arguments(serialized_args)
  133. Arguments.deserialize(serialized_args)
  134. end
  135. 1 def arguments_serialized?
  136. defined?(@serialized_arguments) && @serialized_arguments
  137. end
  138. end
  139. end

target/rubygems/gems/activejob-5.2.3/lib/active_job/enqueuing.rb

45.0% lines covered

20 relevant lines. 9 lines covered and 11 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_job/arguments"
  3. 1 module ActiveJob
  4. # Provides behavior for enqueuing jobs.
  5. 1 module Enqueuing
  6. 1 extend ActiveSupport::Concern
  7. # Includes the +perform_later+ method for job initialization.
  8. 1 module ClassMethods
  9. # Push a job onto the queue. The arguments must be legal JSON types
  10. # (+string+, +int+, +float+, +nil+, +true+, +false+, +hash+ or +array+) or
  11. # GlobalID::Identification instances. Arbitrary Ruby objects
  12. # are not supported.
  13. #
  14. # Returns an instance of the job class queued with arguments available in
  15. # Job#arguments.
  16. 1 def perform_later(*args)
  17. job_or_instantiate(*args).enqueue
  18. end
  19. 1 private
  20. 1 def job_or_instantiate(*args) # :doc:
  21. args.first.is_a?(self) ? args.first : new(*args)
  22. end
  23. end
  24. # Enqueues the job to be performed by the queue adapter.
  25. #
  26. # ==== Options
  27. # * <tt>:wait</tt> - Enqueues the job with the specified delay
  28. # * <tt>:wait_until</tt> - Enqueues the job at the time specified
  29. # * <tt>:queue</tt> - Enqueues the job on the specified queue
  30. # * <tt>:priority</tt> - Enqueues the job with the specified priority
  31. #
  32. # ==== Examples
  33. #
  34. # my_job_instance.enqueue
  35. # my_job_instance.enqueue wait: 5.minutes
  36. # my_job_instance.enqueue queue: :important
  37. # my_job_instance.enqueue wait_until: Date.tomorrow.midnight
  38. # my_job_instance.enqueue priority: 10
  39. 1 def enqueue(options = {})
  40. self.scheduled_at = options[:wait].seconds.from_now.to_f if options[:wait]
  41. self.scheduled_at = options[:wait_until].to_f if options[:wait_until]
  42. self.queue_name = self.class.queue_name_from_part(options[:queue]) if options[:queue]
  43. self.priority = options[:priority].to_i if options[:priority]
  44. run_callbacks :enqueue do
  45. if scheduled_at
  46. self.class.queue_adapter.enqueue_at self, scheduled_at
  47. else
  48. self.class.queue_adapter.enqueue self
  49. end
  50. end
  51. self
  52. end
  53. end
  54. end

target/rubygems/gems/activejob-5.2.3/lib/active_job/exceptions.rb

31.25% lines covered

32 relevant lines. 10 lines covered and 22 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/numeric/time"
  3. 1 module ActiveJob
  4. # Provides behavior for retrying and discarding jobs on exceptions.
  5. 1 module Exceptions
  6. 1 extend ActiveSupport::Concern
  7. 1 module ClassMethods
  8. # Catch the exception and reschedule job for re-execution after so many seconds, for a specific number of attempts.
  9. # If the exception keeps getting raised beyond the specified number of attempts, the exception is allowed to
  10. # bubble up to the underlying queuing system, which may have its own retry mechanism or place it in a
  11. # holding queue for inspection.
  12. #
  13. # You can also pass a block that'll be invoked if the retry attempts fail for custom logic rather than letting
  14. # the exception bubble up. This block is yielded with the job instance as the first and the error instance as the second parameter.
  15. #
  16. # ==== Options
  17. # * <tt>:wait</tt> - Re-enqueues the job with a delay specified either in seconds (default: 3 seconds),
  18. # as a computing proc that the number of executions so far as an argument, or as a symbol reference of
  19. # <tt>:exponentially_longer</tt>, which applies the wait algorithm of <tt>(executions ** 4) + 2</tt>
  20. # (first wait 3s, then 18s, then 83s, etc)
  21. # * <tt>:attempts</tt> - Re-enqueues the job the specified number of times (default: 5 attempts)
  22. # * <tt>:queue</tt> - Re-enqueues the job on a different queue
  23. # * <tt>:priority</tt> - Re-enqueues the job with a different priority
  24. #
  25. # ==== Examples
  26. #
  27. # class RemoteServiceJob < ActiveJob::Base
  28. # retry_on CustomAppException # defaults to 3s wait, 5 attempts
  29. # retry_on AnotherCustomAppException, wait: ->(executions) { executions * 2 }
  30. # retry_on(YetAnotherCustomAppException) do |job, error|
  31. # ExceptionNotifier.caught(error)
  32. # end
  33. # retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3
  34. # retry_on Net::OpenTimeout, wait: :exponentially_longer, attempts: 10
  35. #
  36. # def perform(*args)
  37. # # Might raise CustomAppException, AnotherCustomAppException, or YetAnotherCustomAppException for something domain specific
  38. # # Might raise ActiveRecord::Deadlocked when a local db deadlock is detected
  39. # # Might raise Net::OpenTimeout when the remote service is down
  40. # end
  41. # end
  42. 1 def retry_on(exception, wait: 3.seconds, attempts: 5, queue: nil, priority: nil)
  43. rescue_from exception do |error|
  44. if executions < attempts
  45. logger.error "Retrying #{self.class} in #{wait} seconds, due to a #{exception}. The original exception was #{error.cause.inspect}."
  46. retry_job wait: determine_delay(wait), queue: queue, priority: priority
  47. else
  48. if block_given?
  49. yield self, error
  50. else
  51. logger.error "Stopped retrying #{self.class} due to a #{exception}, which reoccurred on #{executions} attempts. The original exception was #{error.cause.inspect}."
  52. raise error
  53. end
  54. end
  55. end
  56. end
  57. # Discard the job with no attempts to retry, if the exception is raised. This is useful when the subject of the job,
  58. # like an Active Record, is no longer available, and the job is thus no longer relevant.
  59. #
  60. # You can also pass a block that'll be invoked. This block is yielded with the job instance as the first and the error instance as the second parameter.
  61. #
  62. # ==== Example
  63. #
  64. # class SearchIndexingJob < ActiveJob::Base
  65. # discard_on ActiveJob::DeserializationError
  66. # discard_on(CustomAppException) do |job, error|
  67. # ExceptionNotifier.caught(error)
  68. # end
  69. #
  70. # def perform(record)
  71. # # Will raise ActiveJob::DeserializationError if the record can't be deserialized
  72. # # Might raise CustomAppException for something domain specific
  73. # end
  74. # end
  75. 1 def discard_on(exception)
  76. rescue_from exception do |error|
  77. if block_given?
  78. yield self, error
  79. else
  80. logger.error "Discarded #{self.class} due to a #{exception}. The original exception was #{error.cause.inspect}."
  81. end
  82. end
  83. end
  84. end
  85. # Reschedules the job to be re-executed. This is useful in combination
  86. # with the +rescue_from+ option. When you rescue an exception from your job
  87. # you can ask Active Job to retry performing your job.
  88. #
  89. # ==== Options
  90. # * <tt>:wait</tt> - Enqueues the job with the specified delay in seconds
  91. # * <tt>:wait_until</tt> - Enqueues the job at the time specified
  92. # * <tt>:queue</tt> - Enqueues the job on the specified queue
  93. # * <tt>:priority</tt> - Enqueues the job with the specified priority
  94. #
  95. # ==== Examples
  96. #
  97. # class SiteScraperJob < ActiveJob::Base
  98. # rescue_from(ErrorLoadingSite) do
  99. # retry_job queue: :low_priority
  100. # end
  101. #
  102. # def perform(*args)
  103. # # raise ErrorLoadingSite if cannot scrape
  104. # end
  105. # end
  106. 1 def retry_job(options = {})
  107. enqueue options
  108. end
  109. 1 private
  110. 1 def determine_delay(seconds_or_duration_or_algorithm)
  111. case seconds_or_duration_or_algorithm
  112. when :exponentially_longer
  113. (executions**4) + 2
  114. when ActiveSupport::Duration
  115. duration = seconds_or_duration_or_algorithm
  116. duration.to_i
  117. when Integer
  118. seconds = seconds_or_duration_or_algorithm
  119. seconds
  120. when Proc
  121. algorithm = seconds_or_duration_or_algorithm
  122. algorithm.call(executions)
  123. else
  124. raise "Couldn't determine a delay based on #{seconds_or_duration_or_algorithm.inspect}"
  125. end
  126. end
  127. end
  128. end

target/rubygems/gems/activejob-5.2.3/lib/active_job/execution.rb

52.38% lines covered

21 relevant lines. 11 lines covered and 10 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/rescuable"
  3. 1 require "active_job/arguments"
  4. 1 module ActiveJob
  5. 1 module Execution
  6. 1 extend ActiveSupport::Concern
  7. 1 include ActiveSupport::Rescuable
  8. # Includes methods for executing and performing jobs instantly.
  9. 1 module ClassMethods
  10. # Performs the job immediately.
  11. #
  12. # MyJob.perform_now("mike")
  13. #
  14. 1 def perform_now(*args)
  15. job_or_instantiate(*args).perform_now
  16. end
  17. 1 def execute(job_data) #:nodoc:
  18. ActiveJob::Callbacks.run_callbacks(:execute) do
  19. job = deserialize(job_data)
  20. job.perform_now
  21. end
  22. end
  23. end
  24. # Performs the job immediately. The job is not sent to the queueing adapter
  25. # but directly executed by blocking the execution of others until it's finished.
  26. #
  27. # MyJob.new(*args).perform_now
  28. 1 def perform_now
  29. # Guard against jobs that were persisted before we started counting executions by zeroing out nil counters
  30. self.executions = (executions || 0) + 1
  31. deserialize_arguments_if_needed
  32. run_callbacks :perform do
  33. perform(*arguments)
  34. end
  35. rescue => exception
  36. rescue_with_handler(exception) || raise
  37. end
  38. 1 def perform(*)
  39. fail NotImplementedError
  40. end
  41. end
  42. end

target/rubygems/gems/activejob-5.2.3/lib/active_job/logging.rb

38.57% lines covered

70 relevant lines. 27 lines covered and 43 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/hash/transform_values"
  3. 1 require "active_support/core_ext/string/filters"
  4. 1 require "active_support/tagged_logging"
  5. 1 require "active_support/logger"
  6. 1 module ActiveJob
  7. 1 module Logging #:nodoc:
  8. 1 extend ActiveSupport::Concern
  9. 1 included do
  10. 1 cattr_accessor :logger, default: ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT))
  11. 1 around_enqueue do |_, block, _|
  12. tag_logger do
  13. block.call
  14. end
  15. end
  16. 1 around_perform do |job, block, _|
  17. tag_logger(job.class.name, job.job_id) do
  18. payload = { adapter: job.class.queue_adapter, job: job }
  19. ActiveSupport::Notifications.instrument("perform_start.active_job", payload.dup)
  20. ActiveSupport::Notifications.instrument("perform.active_job", payload) do
  21. block.call
  22. end
  23. end
  24. end
  25. 1 after_enqueue do |job|
  26. if job.scheduled_at
  27. ActiveSupport::Notifications.instrument "enqueue_at.active_job",
  28. adapter: job.class.queue_adapter, job: job
  29. else
  30. ActiveSupport::Notifications.instrument "enqueue.active_job",
  31. adapter: job.class.queue_adapter, job: job
  32. end
  33. end
  34. end
  35. 1 private
  36. 1 def tag_logger(*tags)
  37. if logger.respond_to?(:tagged)
  38. tags.unshift "ActiveJob" unless logger_tagged_by_active_job?
  39. logger.tagged(*tags) { yield }
  40. else
  41. yield
  42. end
  43. end
  44. 1 def logger_tagged_by_active_job?
  45. logger.formatter.current_tags.include?("ActiveJob")
  46. end
  47. 1 class LogSubscriber < ActiveSupport::LogSubscriber #:nodoc:
  48. 1 def enqueue(event)
  49. info do
  50. job = event.payload[:job]
  51. "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)}" + args_info(job)
  52. end
  53. end
  54. 1 def enqueue_at(event)
  55. info do
  56. job = event.payload[:job]
  57. "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)} at #{scheduled_at(event)}" + args_info(job)
  58. end
  59. end
  60. 1 def perform_start(event)
  61. info do
  62. job = event.payload[:job]
  63. "Performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)}" + args_info(job)
  64. end
  65. end
  66. 1 def perform(event)
  67. job = event.payload[:job]
  68. ex = event.payload[:exception_object]
  69. if ex
  70. error do
  71. "Error performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms: #{ex.class} (#{ex.message}):\n" + Array(ex.backtrace).join("\n")
  72. end
  73. else
  74. info do
  75. "Performed #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms"
  76. end
  77. end
  78. end
  79. 1 private
  80. 1 def queue_name(event)
  81. event.payload[:adapter].class.name.demodulize.remove("Adapter") + "(#{event.payload[:job].queue_name})"
  82. end
  83. 1 def args_info(job)
  84. if job.arguments.any?
  85. " with arguments: " +
  86. job.arguments.map { |arg| format(arg).inspect }.join(", ")
  87. else
  88. ""
  89. end
  90. end
  91. 1 def format(arg)
  92. case arg
  93. when Hash
  94. arg.transform_values { |value| format(value) }
  95. when Array
  96. arg.map { |value| format(value) }
  97. when GlobalID::Identification
  98. arg.to_global_id rescue arg
  99. else
  100. arg
  101. end
  102. end
  103. 1 def scheduled_at(event)
  104. Time.at(event.payload[:job].scheduled_at).utc
  105. end
  106. 1 def logger
  107. ActiveJob::Base.logger
  108. end
  109. end
  110. end
  111. end
  112. 1 ActiveJob::Logging::LogSubscriber.attach_to :active_job

target/rubygems/gems/activejob-5.2.3/lib/active_job/queue_adapter.rb

75.0% lines covered

28 relevant lines. 21 lines covered and 7 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/string/inflections"
  3. 1 module ActiveJob
  4. # The <tt>ActiveJob::QueueAdapter</tt> module is used to load the
  5. # correct adapter. The default queue adapter is the +:async+ queue.
  6. 1 module QueueAdapter #:nodoc:
  7. 1 extend ActiveSupport::Concern
  8. 1 included do
  9. 1 class_attribute :_queue_adapter_name, instance_accessor: false, instance_predicate: false
  10. 1 class_attribute :_queue_adapter, instance_accessor: false, instance_predicate: false
  11. 1 self.queue_adapter = :async
  12. end
  13. # Includes the setter method for changing the active queue adapter.
  14. 1 module ClassMethods
  15. # Returns the backend queue provider. The default queue adapter
  16. # is the +:async+ queue. See QueueAdapters for more information.
  17. 1 def queue_adapter
  18. _queue_adapter
  19. end
  20. 1 def queue_adapter_name
  21. _queue_adapter_name
  22. end
  23. # Specify the backend queue provider. The default queue adapter
  24. # is the +:async+ queue. See QueueAdapters for more
  25. # information.
  26. 1 def queue_adapter=(name_or_adapter)
  27. 2 case name_or_adapter
  28. when Symbol, String
  29. 2 queue_adapter = ActiveJob::QueueAdapters.lookup(name_or_adapter).new
  30. 2 assign_adapter(name_or_adapter.to_s, queue_adapter)
  31. else
  32. if queue_adapter?(name_or_adapter)
  33. adapter_name = "#{name_or_adapter.class.name.demodulize.remove('Adapter').underscore}"
  34. assign_adapter(adapter_name, name_or_adapter)
  35. else
  36. raise ArgumentError
  37. end
  38. end
  39. end
  40. 1 private
  41. 1 def assign_adapter(adapter_name, queue_adapter)
  42. 2 self._queue_adapter_name = adapter_name
  43. 2 self._queue_adapter = queue_adapter
  44. end
  45. 1 QUEUE_ADAPTER_METHODS = [:enqueue, :enqueue_at].freeze
  46. 1 def queue_adapter?(object)
  47. QUEUE_ADAPTER_METHODS.all? { |meth| object.respond_to?(meth) }
  48. end
  49. end
  50. end
  51. end

target/rubygems/gems/activejob-5.2.3/lib/active_job/queue_adapters.rb

100.0% lines covered

20 relevant lines. 20 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveJob
  3. # == Active Job adapters
  4. #
  5. # Active Job has adapters for the following queueing backends:
  6. #
  7. # * {Backburner}[https://github.com/nesquena/backburner]
  8. # * {Delayed Job}[https://github.com/collectiveidea/delayed_job]
  9. # * {Qu}[https://github.com/bkeepers/qu]
  10. # * {Que}[https://github.com/chanks/que]
  11. # * {queue_classic}[https://github.com/QueueClassic/queue_classic]
  12. # * {Resque}[https://github.com/resque/resque]
  13. # * {Sidekiq}[http://sidekiq.org]
  14. # * {Sneakers}[https://github.com/jondot/sneakers]
  15. # * {Sucker Punch}[https://github.com/brandonhilkert/sucker_punch]
  16. # * {Active Job Async Job}[http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/AsyncAdapter.html]
  17. # * {Active Job Inline}[http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/InlineAdapter.html]
  18. #
  19. # === Backends Features
  20. #
  21. # | | Async | Queues | Delayed | Priorities | Timeout | Retries |
  22. # |-------------------|-------|--------|------------|------------|---------|---------|
  23. # | Backburner | Yes | Yes | Yes | Yes | Job | Global |
  24. # | Delayed Job | Yes | Yes | Yes | Job | Global | Global |
  25. # | Qu | Yes | Yes | No | No | No | Global |
  26. # | Que | Yes | Yes | Yes | Job | No | Job |
  27. # | queue_classic | Yes | Yes | Yes* | No | No | No |
  28. # | Resque | Yes | Yes | Yes (Gem) | Queue | Global | Yes |
  29. # | Sidekiq | Yes | Yes | Yes | Queue | No | Job |
  30. # | Sneakers | Yes | Yes | No | Queue | Queue | No |
  31. # | Sucker Punch | Yes | Yes | Yes | No | No | No |
  32. # | Active Job Async | Yes | Yes | Yes | No | No | No |
  33. # | Active Job Inline | No | Yes | N/A | N/A | N/A | N/A |
  34. #
  35. # ==== Async
  36. #
  37. # Yes: The Queue Adapter has the ability to run the job in a non-blocking manner.
  38. # It either runs on a separate or forked process, or on a different thread.
  39. #
  40. # No: The job is run in the same process.
  41. #
  42. # ==== Queues
  43. #
  44. # Yes: Jobs may set which queue they are run in with queue_as or by using the set
  45. # method.
  46. #
  47. # ==== Delayed
  48. #
  49. # Yes: The adapter will run the job in the future through perform_later.
  50. #
  51. # (Gem): An additional gem is required to use perform_later with this adapter.
  52. #
  53. # No: The adapter will run jobs at the next opportunity and cannot use perform_later.
  54. #
  55. # N/A: The adapter does not support queueing.
  56. #
  57. # NOTE:
  58. # queue_classic supports job scheduling since version 3.1.
  59. # For older versions you can use the queue_classic-later gem.
  60. #
  61. # ==== Priorities
  62. #
  63. # The order in which jobs are processed can be configured differently depending
  64. # on the adapter.
  65. #
  66. # Job: Any class inheriting from the adapter may set the priority on the job
  67. # object relative to other jobs.
  68. #
  69. # Queue: The adapter can set the priority for job queues, when setting a queue
  70. # with Active Job this will be respected.
  71. #
  72. # Yes: Allows the priority to be set on the job object, at the queue level or
  73. # as default configuration option.
  74. #
  75. # No: Does not allow the priority of jobs to be configured.
  76. #
  77. # N/A: The adapter does not support queueing, and therefore sorting them.
  78. #
  79. # ==== Timeout
  80. #
  81. # When a job will stop after the allotted time.
  82. #
  83. # Job: The timeout can be set for each instance of the job class.
  84. #
  85. # Queue: The timeout is set for all jobs on the queue.
  86. #
  87. # Global: The adapter is configured that all jobs have a maximum run time.
  88. #
  89. # N/A: This adapter does not run in a separate process, and therefore timeout
  90. # is unsupported.
  91. #
  92. # ==== Retries
  93. #
  94. # Job: The number of retries can be set per instance of the job class.
  95. #
  96. # Yes: The Number of retries can be configured globally, for each instance or
  97. # on the queue. This adapter may also present failed instances of the job class
  98. # that can be restarted.
  99. #
  100. # Global: The adapter has a global number of retries.
  101. #
  102. # N/A: The adapter does not run in a separate process, and therefore doesn't
  103. # support retries.
  104. #
  105. # === Async and Inline Queue Adapters
  106. #
  107. # Active Job has two built-in queue adapters intended for development and
  108. # testing: +:async+ and +:inline+.
  109. 1 module QueueAdapters
  110. 1 extend ActiveSupport::Autoload
  111. 1 autoload :AsyncAdapter
  112. 1 autoload :InlineAdapter
  113. 1 autoload :BackburnerAdapter
  114. 1 autoload :DelayedJobAdapter
  115. 1 autoload :QuAdapter
  116. 1 autoload :QueAdapter
  117. 1 autoload :QueueClassicAdapter
  118. 1 autoload :ResqueAdapter
  119. 1 autoload :SidekiqAdapter
  120. 1 autoload :SneakersAdapter
  121. 1 autoload :SuckerPunchAdapter
  122. 1 autoload :TestAdapter
  123. 1 ADAPTER = "Adapter".freeze
  124. 1 private_constant :ADAPTER
  125. 1 class << self
  126. # Returns adapter for specified name.
  127. #
  128. # ActiveJob::QueueAdapters.lookup(:sidekiq)
  129. # # => ActiveJob::QueueAdapters::SidekiqAdapter
  130. 1 def lookup(name)
  131. 2 const_get(name.to_s.camelize << ADAPTER)
  132. end
  133. end
  134. end
  135. end

target/rubygems/gems/activejob-5.2.3/lib/active_job/queue_adapters/async_adapter.rb

59.09% lines covered

44 relevant lines. 26 lines covered and 18 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "securerandom"
  3. 1 require "concurrent/scheduled_task"
  4. 1 require "concurrent/executor/thread_pool_executor"
  5. 1 require "concurrent/utility/processor_counter"
  6. 1 module ActiveJob
  7. 1 module QueueAdapters
  8. # == Active Job Async adapter
  9. #
  10. # The Async adapter runs jobs with an in-process thread pool.
  11. #
  12. # This is the default queue adapter. It's well-suited for dev/test since
  13. # it doesn't need an external infrastructure, but it's a poor fit for
  14. # production since it drops pending jobs on restart.
  15. #
  16. # To use this adapter, set queue adapter to +:async+:
  17. #
  18. # config.active_job.queue_adapter = :async
  19. #
  20. # To configure the adapter's thread pool, instantiate the adapter and
  21. # pass your own config:
  22. #
  23. # config.active_job.queue_adapter = ActiveJob::QueueAdapters::AsyncAdapter.new \
  24. # min_threads: 1,
  25. # max_threads: 2 * Concurrent.processor_count,
  26. # idletime: 600.seconds
  27. #
  28. # The adapter uses a {Concurrent Ruby}[https://github.com/ruby-concurrency/concurrent-ruby] thread pool to schedule and execute
  29. # jobs. Since jobs share a single thread pool, long-running jobs will block
  30. # short-lived jobs. Fine for dev/test; bad for production.
  31. 1 class AsyncAdapter
  32. # See {Concurrent::ThreadPoolExecutor}[https://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ThreadPoolExecutor.html] for executor options.
  33. 1 def initialize(**executor_options)
  34. 2 @scheduler = Scheduler.new(**executor_options)
  35. end
  36. 1 def enqueue(job) #:nodoc:
  37. @scheduler.enqueue JobWrapper.new(job), queue_name: job.queue_name
  38. end
  39. 1 def enqueue_at(job, timestamp) #:nodoc:
  40. @scheduler.enqueue_at JobWrapper.new(job), timestamp, queue_name: job.queue_name
  41. end
  42. # Gracefully stop processing jobs. Finishes in-progress work and handles
  43. # any new jobs following the executor's fallback policy (`caller_runs`).
  44. # Waits for termination by default. Pass `wait: false` to continue.
  45. 1 def shutdown(wait: true) #:nodoc:
  46. @scheduler.shutdown wait: wait
  47. end
  48. # Used for our test suite.
  49. 1 def immediate=(immediate) #:nodoc:
  50. @scheduler.immediate = immediate
  51. end
  52. # Note that we don't actually need to serialize the jobs since we're
  53. # performing them in-process, but we do so anyway for parity with other
  54. # adapters and deployment environments. Otherwise, serialization bugs
  55. # may creep in undetected.
  56. 1 class JobWrapper #:nodoc:
  57. 1 def initialize(job)
  58. job.provider_job_id = SecureRandom.uuid
  59. @job_data = job.serialize
  60. end
  61. 1 def perform
  62. Base.execute @job_data
  63. end
  64. end
  65. 1 class Scheduler #:nodoc:
  66. DEFAULT_EXECUTOR_OPTIONS = {
  67. min_threads: 0,
  68. max_threads: Concurrent.processor_count,
  69. auto_terminate: true,
  70. idletime: 60, # 1 minute
  71. max_queue: 0, # unlimited
  72. fallback_policy: :caller_runs # shouldn't matter -- 0 max queue
  73. }.freeze
  74. 1 attr_accessor :immediate
  75. 1 def initialize(**options)
  76. 2 self.immediate = false
  77. 2 @immediate_executor = Concurrent::ImmediateExecutor.new
  78. 2 @async_executor = Concurrent::ThreadPoolExecutor.new(DEFAULT_EXECUTOR_OPTIONS.merge(options))
  79. end
  80. 1 def enqueue(job, queue_name:)
  81. executor.post(job, &:perform)
  82. end
  83. 1 def enqueue_at(job, timestamp, queue_name:)
  84. delay = timestamp - Time.current.to_f
  85. if delay > 0
  86. Concurrent::ScheduledTask.execute(delay, args: [job], executor: executor, &:perform)
  87. else
  88. enqueue(job, queue_name: queue_name)
  89. end
  90. end
  91. 1 def shutdown(wait: true)
  92. @async_executor.shutdown
  93. @async_executor.wait_for_termination if wait
  94. end
  95. 1 def executor
  96. immediate ? @immediate_executor : @async_executor
  97. end
  98. end
  99. end
  100. end
  101. end

target/rubygems/gems/activejob-5.2.3/lib/active_job/queue_name.rb

66.67% lines covered

21 relevant lines. 14 lines covered and 7 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveJob
  3. 1 module QueueName
  4. 1 extend ActiveSupport::Concern
  5. # Includes the ability to override the default queue name and prefix.
  6. 1 module ClassMethods
  7. 1 mattr_accessor :queue_name_prefix
  8. 1 mattr_accessor :default_queue_name, default: "default"
  9. # Specifies the name of the queue to process the job on.
  10. #
  11. # class PublishToFeedJob < ActiveJob::Base
  12. # queue_as :feeds
  13. #
  14. # def perform(post)
  15. # post.to_feed!
  16. # end
  17. # end
  18. 1 def queue_as(part_name = nil, &block)
  19. 1 if block_given?
  20. 1 self.queue_name = block
  21. else
  22. self.queue_name = queue_name_from_part(part_name)
  23. end
  24. end
  25. 1 def queue_name_from_part(part_name) #:nodoc:
  26. queue_name = part_name || default_queue_name
  27. name_parts = [queue_name_prefix.presence, queue_name]
  28. name_parts.compact.join(queue_name_delimiter)
  29. end
  30. end
  31. 1 included do
  32. 1 class_attribute :queue_name, instance_accessor: false, default: default_queue_name
  33. 1 class_attribute :queue_name_delimiter, instance_accessor: false, default: "_"
  34. end
  35. # Returns the name of the queue the job will be run on.
  36. 1 def queue_name
  37. if @queue_name.is_a?(Proc)
  38. @queue_name = self.class.queue_name_from_part(instance_exec(&@queue_name))
  39. end
  40. @queue_name
  41. end
  42. end
  43. end

target/rubygems/gems/activejob-5.2.3/lib/active_job/queue_priority.rb

60.0% lines covered

15 relevant lines. 9 lines covered and 6 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveJob
  3. 1 module QueuePriority
  4. 1 extend ActiveSupport::Concern
  5. # Includes the ability to override the default queue priority.
  6. 1 module ClassMethods
  7. 1 mattr_accessor :default_priority
  8. # Specifies the priority of the queue to create the job with.
  9. #
  10. # class PublishToFeedJob < ActiveJob::Base
  11. # queue_with_priority 50
  12. #
  13. # def perform(post)
  14. # post.to_feed!
  15. # end
  16. # end
  17. #
  18. # Specify either an argument or a block.
  19. 1 def queue_with_priority(priority = nil, &block)
  20. if block_given?
  21. self.priority = block
  22. else
  23. self.priority = priority
  24. end
  25. end
  26. end
  27. 1 included do
  28. 1 class_attribute :priority, instance_accessor: false, default: default_priority
  29. end
  30. # Returns the priority that the job will be created with
  31. 1 def priority
  32. if @priority.is_a?(Proc)
  33. @priority = instance_exec(&@priority)
  34. end
  35. @priority
  36. end
  37. end
  38. end

target/rubygems/gems/activejob-5.2.3/lib/active_job/test_helper.rb

27.87% lines covered

122 relevant lines. 34 lines covered and 88 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/class/subclasses"
  3. 1 require "active_support/core_ext/hash/keys"
  4. 1 module ActiveJob
  5. # Provides helper methods for testing Active Job
  6. 1 module TestHelper
  7. 1 delegate :enqueued_jobs, :enqueued_jobs=,
  8. :performed_jobs, :performed_jobs=,
  9. to: :queue_adapter
  10. 1 module TestQueueAdapter
  11. 1 extend ActiveSupport::Concern
  12. 1 included do
  13. 1 class_attribute :_test_adapter, instance_accessor: false, instance_predicate: false
  14. end
  15. 1 module ClassMethods
  16. 1 def queue_adapter
  17. self._test_adapter.nil? ? super : self._test_adapter
  18. end
  19. 1 def disable_test_adapter
  20. self._test_adapter = nil
  21. end
  22. 1 def enable_test_adapter(test_adapter)
  23. self._test_adapter = test_adapter
  24. end
  25. end
  26. end
  27. 1 ActiveJob::Base.include(TestQueueAdapter)
  28. 1 def before_setup # :nodoc:
  29. test_adapter = queue_adapter_for_test
  30. queue_adapter_changed_jobs.each do |klass|
  31. klass.enable_test_adapter(test_adapter)
  32. end
  33. clear_enqueued_jobs
  34. clear_performed_jobs
  35. super
  36. end
  37. 1 def after_teardown # :nodoc:
  38. super
  39. queue_adapter_changed_jobs.each { |klass| klass.disable_test_adapter }
  40. end
  41. # Specifies the queue adapter to use with all active job test helpers.
  42. #
  43. # Returns an instance of the queue adapter and defaults to
  44. # <tt>ActiveJob::QueueAdapters::TestAdapter</tt>.
  45. #
  46. # Note: The adapter provided by this method must provide some additional
  47. # methods from those expected of a standard <tt>ActiveJob::QueueAdapter</tt>
  48. # in order to be used with the active job test helpers. Refer to
  49. # <tt>ActiveJob::QueueAdapters::TestAdapter</tt>.
  50. 1 def queue_adapter_for_test
  51. ActiveJob::QueueAdapters::TestAdapter.new
  52. end
  53. # Asserts that the number of enqueued jobs matches the given number.
  54. #
  55. # def test_jobs
  56. # assert_enqueued_jobs 0
  57. # HelloJob.perform_later('david')
  58. # assert_enqueued_jobs 1
  59. # HelloJob.perform_later('abdelkader')
  60. # assert_enqueued_jobs 2
  61. # end
  62. #
  63. # If a block is passed, that block will cause the specified number of
  64. # jobs to be enqueued.
  65. #
  66. # def test_jobs_again
  67. # assert_enqueued_jobs 1 do
  68. # HelloJob.perform_later('cristian')
  69. # end
  70. #
  71. # assert_enqueued_jobs 2 do
  72. # HelloJob.perform_later('aaron')
  73. # HelloJob.perform_later('rafael')
  74. # end
  75. # end
  76. #
  77. # The number of times a specific job was enqueued can be asserted.
  78. #
  79. # def test_logging_job
  80. # assert_enqueued_jobs 1, only: LoggingJob do
  81. # LoggingJob.perform_later
  82. # HelloJob.perform_later('jeremy')
  83. # end
  84. # end
  85. #
  86. # The number of times a job except specific class was enqueued can be asserted.
  87. #
  88. # def test_logging_job
  89. # assert_enqueued_jobs 1, except: HelloJob do
  90. # LoggingJob.perform_later
  91. # HelloJob.perform_later('jeremy')
  92. # end
  93. # end
  94. #
  95. # The number of times a job is enqueued to a specific queue can also be asserted.
  96. #
  97. # def test_logging_job
  98. # assert_enqueued_jobs 2, queue: 'default' do
  99. # LoggingJob.perform_later
  100. # HelloJob.perform_later('elfassy')
  101. # end
  102. # end
  103. 1 def assert_enqueued_jobs(number, only: nil, except: nil, queue: nil)
  104. if block_given?
  105. original_count = enqueued_jobs_size(only: only, except: except, queue: queue)
  106. yield
  107. new_count = enqueued_jobs_size(only: only, except: except, queue: queue)
  108. assert_equal number, new_count - original_count, "#{number} jobs expected, but #{new_count - original_count} were enqueued"
  109. else
  110. actual_count = enqueued_jobs_size(only: only, except: except, queue: queue)
  111. assert_equal number, actual_count, "#{number} jobs expected, but #{actual_count} were enqueued"
  112. end
  113. end
  114. # Asserts that no jobs have been enqueued.
  115. #
  116. # def test_jobs
  117. # assert_no_enqueued_jobs
  118. # HelloJob.perform_later('jeremy')
  119. # assert_enqueued_jobs 1
  120. # end
  121. #
  122. # If a block is passed, that block should not cause any job to be enqueued.
  123. #
  124. # def test_jobs_again
  125. # assert_no_enqueued_jobs do
  126. # # No job should be enqueued from this block
  127. # end
  128. # end
  129. #
  130. # It can be asserted that no jobs of a specific kind are enqueued:
  131. #
  132. # def test_no_logging
  133. # assert_no_enqueued_jobs only: LoggingJob do
  134. # HelloJob.perform_later('jeremy')
  135. # end
  136. # end
  137. #
  138. # It can be asserted that no jobs except specific class are enqueued:
  139. #
  140. # def test_no_logging
  141. # assert_no_enqueued_jobs except: HelloJob do
  142. # HelloJob.perform_later('jeremy')
  143. # end
  144. # end
  145. #
  146. # Note: This assertion is simply a shortcut for:
  147. #
  148. # assert_enqueued_jobs 0, &block
  149. 1 def assert_no_enqueued_jobs(only: nil, except: nil, &block)
  150. assert_enqueued_jobs 0, only: only, except: except, &block
  151. end
  152. # Asserts that the number of performed jobs matches the given number.
  153. # If no block is passed, <tt>perform_enqueued_jobs</tt>
  154. # must be called around the job call.
  155. #
  156. # def test_jobs
  157. # assert_performed_jobs 0
  158. #
  159. # perform_enqueued_jobs do
  160. # HelloJob.perform_later('xavier')
  161. # end
  162. # assert_performed_jobs 1
  163. #
  164. # perform_enqueued_jobs do
  165. # HelloJob.perform_later('yves')
  166. # assert_performed_jobs 2
  167. # end
  168. # end
  169. #
  170. # If a block is passed, that block should cause the specified number of
  171. # jobs to be performed.
  172. #
  173. # def test_jobs_again
  174. # assert_performed_jobs 1 do
  175. # HelloJob.perform_later('robin')
  176. # end
  177. #
  178. # assert_performed_jobs 2 do
  179. # HelloJob.perform_later('carlos')
  180. # HelloJob.perform_later('sean')
  181. # end
  182. # end
  183. #
  184. # The block form supports filtering. If the :only option is specified,
  185. # then only the listed job(s) will be performed.
  186. #
  187. # def test_hello_job
  188. # assert_performed_jobs 1, only: HelloJob do
  189. # HelloJob.perform_later('jeremy')
  190. # LoggingJob.perform_later
  191. # end
  192. # end
  193. #
  194. # Also if the :except option is specified,
  195. # then the job(s) except specific class will be performed.
  196. #
  197. # def test_hello_job
  198. # assert_performed_jobs 1, except: LoggingJob do
  199. # HelloJob.perform_later('jeremy')
  200. # LoggingJob.perform_later
  201. # end
  202. # end
  203. #
  204. # An array may also be specified, to support testing multiple jobs.
  205. #
  206. # def test_hello_and_logging_jobs
  207. # assert_nothing_raised do
  208. # assert_performed_jobs 2, only: [HelloJob, LoggingJob] do
  209. # HelloJob.perform_later('jeremy')
  210. # LoggingJob.perform_later('stewie')
  211. # RescueJob.perform_later('david')
  212. # end
  213. # end
  214. # end
  215. 1 def assert_performed_jobs(number, only: nil, except: nil)
  216. if block_given?
  217. original_count = performed_jobs.size
  218. perform_enqueued_jobs(only: only, except: except) { yield }
  219. new_count = performed_jobs.size
  220. assert_equal number, new_count - original_count,
  221. "#{number} jobs expected, but #{new_count - original_count} were performed"
  222. else
  223. performed_jobs_size = performed_jobs.size
  224. assert_equal number, performed_jobs_size, "#{number} jobs expected, but #{performed_jobs_size} were performed"
  225. end
  226. end
  227. # Asserts that no jobs have been performed.
  228. #
  229. # def test_jobs
  230. # assert_no_performed_jobs
  231. #
  232. # perform_enqueued_jobs do
  233. # HelloJob.perform_later('matthew')
  234. # assert_performed_jobs 1
  235. # end
  236. # end
  237. #
  238. # If a block is passed, that block should not cause any job to be performed.
  239. #
  240. # def test_jobs_again
  241. # assert_no_performed_jobs do
  242. # # No job should be performed from this block
  243. # end
  244. # end
  245. #
  246. # The block form supports filtering. If the :only option is specified,
  247. # then only the listed job(s) will not be performed.
  248. #
  249. # def test_no_logging
  250. # assert_no_performed_jobs only: LoggingJob do
  251. # HelloJob.perform_later('jeremy')
  252. # end
  253. # end
  254. #
  255. # Also if the :except option is specified,
  256. # then the job(s) except specific class will not be performed.
  257. #
  258. # def test_no_logging
  259. # assert_no_performed_jobs except: HelloJob do
  260. # HelloJob.perform_later('jeremy')
  261. # end
  262. # end
  263. #
  264. # Note: This assertion is simply a shortcut for:
  265. #
  266. # assert_performed_jobs 0, &block
  267. 1 def assert_no_performed_jobs(only: nil, except: nil, &block)
  268. assert_performed_jobs 0, only: only, except: except, &block
  269. end
  270. # Asserts that the job passed in the block has been enqueued with the given arguments.
  271. #
  272. # def test_assert_enqueued_with
  273. # assert_enqueued_with(job: MyJob, args: [1,2,3], queue: 'low') do
  274. # MyJob.perform_later(1,2,3)
  275. # end
  276. #
  277. # assert_enqueued_with(job: MyJob, at: Date.tomorrow.noon) do
  278. # MyJob.set(wait_until: Date.tomorrow.noon).perform_later
  279. # end
  280. # end
  281. 1 def assert_enqueued_with(job: nil, args: nil, at: nil, queue: nil)
  282. original_enqueued_jobs_count = enqueued_jobs.count
  283. expected = { job: job, args: args, at: at, queue: queue }.compact
  284. expected_args = prepare_args_for_assertion(expected)
  285. yield
  286. in_block_jobs = enqueued_jobs.drop(original_enqueued_jobs_count)
  287. matching_job = in_block_jobs.find do |in_block_job|
  288. deserialized_job = deserialize_args_for_assertion(in_block_job)
  289. expected_args.all? { |key, value| value == deserialized_job[key] }
  290. end
  291. assert matching_job, "No enqueued job found with #{expected}"
  292. instantiate_job(matching_job)
  293. end
  294. # Asserts that the job passed in the block has been performed with the given arguments.
  295. #
  296. # def test_assert_performed_with
  297. # assert_performed_with(job: MyJob, args: [1,2,3], queue: 'high') do
  298. # MyJob.perform_later(1,2,3)
  299. # end
  300. #
  301. # assert_performed_with(job: MyJob, at: Date.tomorrow.noon) do
  302. # MyJob.set(wait_until: Date.tomorrow.noon).perform_later
  303. # end
  304. # end
  305. 1 def assert_performed_with(job: nil, args: nil, at: nil, queue: nil)
  306. original_performed_jobs_count = performed_jobs.count
  307. expected = { job: job, args: args, at: at, queue: queue }.compact
  308. expected_args = prepare_args_for_assertion(expected)
  309. perform_enqueued_jobs { yield }
  310. in_block_jobs = performed_jobs.drop(original_performed_jobs_count)
  311. matching_job = in_block_jobs.find do |in_block_job|
  312. deserialized_job = deserialize_args_for_assertion(in_block_job)
  313. expected_args.all? { |key, value| value == deserialized_job[key] }
  314. end
  315. assert matching_job, "No performed job found with #{expected}"
  316. instantiate_job(matching_job)
  317. end
  318. # Performs all enqueued jobs in the duration of the block.
  319. #
  320. # def test_perform_enqueued_jobs
  321. # perform_enqueued_jobs do
  322. # MyJob.perform_later(1, 2, 3)
  323. # end
  324. # assert_performed_jobs 1
  325. # end
  326. #
  327. # This method also supports filtering. If the +:only+ option is specified,
  328. # then only the listed job(s) will be performed.
  329. #
  330. # def test_perform_enqueued_jobs_with_only
  331. # perform_enqueued_jobs(only: MyJob) do
  332. # MyJob.perform_later(1, 2, 3) # will be performed
  333. # HelloJob.perform_later(1, 2, 3) # will not be performed
  334. # end
  335. # assert_performed_jobs 1
  336. # end
  337. #
  338. # Also if the +:except+ option is specified,
  339. # then the job(s) except specific class will be performed.
  340. #
  341. # def test_perform_enqueued_jobs_with_except
  342. # perform_enqueued_jobs(except: HelloJob) do
  343. # MyJob.perform_later(1, 2, 3) # will be performed
  344. # HelloJob.perform_later(1, 2, 3) # will not be performed
  345. # end
  346. # assert_performed_jobs 1
  347. # end
  348. #
  349. 1 def perform_enqueued_jobs(only: nil, except: nil)
  350. validate_option(only: only, except: except)
  351. old_perform_enqueued_jobs = queue_adapter.perform_enqueued_jobs
  352. old_perform_enqueued_at_jobs = queue_adapter.perform_enqueued_at_jobs
  353. old_filter = queue_adapter.filter
  354. old_reject = queue_adapter.reject
  355. begin
  356. queue_adapter.perform_enqueued_jobs = true
  357. queue_adapter.perform_enqueued_at_jobs = true
  358. queue_adapter.filter = only
  359. queue_adapter.reject = except
  360. yield
  361. ensure
  362. queue_adapter.perform_enqueued_jobs = old_perform_enqueued_jobs
  363. queue_adapter.perform_enqueued_at_jobs = old_perform_enqueued_at_jobs
  364. queue_adapter.filter = old_filter
  365. queue_adapter.reject = old_reject
  366. end
  367. end
  368. # Accesses the queue_adapter set by ActiveJob::Base.
  369. #
  370. # def test_assert_job_has_custom_queue_adapter_set
  371. # assert_instance_of CustomQueueAdapter, HelloJob.queue_adapter
  372. # end
  373. 1 def queue_adapter
  374. ActiveJob::Base.queue_adapter
  375. end
  376. 1 private
  377. 1 def clear_enqueued_jobs
  378. enqueued_jobs.clear
  379. end
  380. 1 def clear_performed_jobs
  381. performed_jobs.clear
  382. end
  383. 1 def enqueued_jobs_size(only: nil, except: nil, queue: nil)
  384. validate_option(only: only, except: except)
  385. enqueued_jobs.count do |job|
  386. job_class = job.fetch(:job)
  387. if only
  388. next false unless Array(only).include?(job_class)
  389. elsif except
  390. next false if Array(except).include?(job_class)
  391. end
  392. if queue
  393. next false unless queue.to_s == job.fetch(:queue, job_class.queue_name)
  394. end
  395. true
  396. end
  397. end
  398. 1 def prepare_args_for_assertion(args)
  399. args.dup.tap do |arguments|
  400. arguments[:at] = arguments[:at].to_f if arguments[:at]
  401. end
  402. end
  403. 1 def deserialize_args_for_assertion(job)
  404. job.dup.tap do |new_job|
  405. new_job[:args] = ActiveJob::Arguments.deserialize(new_job[:args]) if new_job[:args]
  406. end
  407. end
  408. 1 def instantiate_job(payload)
  409. args = ActiveJob::Arguments.deserialize(payload[:args])
  410. job = payload[:job].new(*args)
  411. job.scheduled_at = Time.at(payload[:at]) if payload.key?(:at)
  412. job.queue_name = payload[:queue]
  413. job
  414. end
  415. 1 def queue_adapter_changed_jobs
  416. (ActiveJob::Base.descendants << ActiveJob::Base).select do |klass|
  417. # only override explicitly set adapters, a quirk of `class_attribute`
  418. klass.singleton_class.public_instance_methods(false).include?(:_queue_adapter)
  419. end
  420. end
  421. 1 def validate_option(only: nil, except: nil)
  422. raise ArgumentError, "Cannot specify both `:only` and `:except` options." if only && except
  423. end
  424. end
  425. end

target/rubygems/gems/activejob-5.2.3/lib/active_job/translation.rb

83.33% lines covered

6 relevant lines. 5 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveJob
  3. 1 module Translation #:nodoc:
  4. 1 extend ActiveSupport::Concern
  5. 1 included do
  6. 1 around_perform do |job, block, _|
  7. I18n.with_locale(job.locale, &block)
  8. end
  9. end
  10. end
  11. end

target/rubygems/gems/activerecord-5.2.3/lib/active_record/association_relation.rb

52.38% lines covered

21 relevant lines. 11 lines covered and 10 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveRecord
  3. 1 class AssociationRelation < Relation
  4. 1 def initialize(klass, association)
  5. super(klass)
  6. @association = association
  7. end
  8. 1 def proxy_association
  9. @association
  10. end
  11. 1 def ==(other)
  12. other == records
  13. end
  14. 1 def build(*args, &block)
  15. scoping { @association.build(*args, &block) }
  16. end
  17. 1 alias new build
  18. 1 def create(*args, &block)
  19. scoping { @association.create(*args, &block) }
  20. end
  21. 1 def create!(*args, &block)
  22. scoping { @association.create!(*args, &block) }
  23. end
  24. 1 private
  25. 1 def exec_queries
  26. super do |record|
  27. @association.set_inverse_instance_from_queries(record)
  28. yield record if block_given?
  29. end
  30. end
  31. end
  32. end

target/rubygems/gems/activerecord-5.2.3/lib/active_record/associations/collection_proxy.rb

47.42% lines covered

97 relevant lines. 46 lines covered and 51 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveRecord
  3. 1 module Associations
  4. # Association proxies in Active Record are middlemen between the object that
  5. # holds the association, known as the <tt>@owner</tt>, and the actual associated
  6. # object, known as the <tt>@target</tt>. The kind of association any proxy is
  7. # about is available in <tt>@reflection</tt>. That's an instance of the class
  8. # ActiveRecord::Reflection::AssociationReflection.
  9. #
  10. # For example, given
  11. #
  12. # class Blog < ActiveRecord::Base
  13. # has_many :posts
  14. # end
  15. #
  16. # blog = Blog.first
  17. #
  18. # the association proxy in <tt>blog.posts</tt> has the object in +blog+ as
  19. # <tt>@owner</tt>, the collection of its posts as <tt>@target</tt>, and
  20. # the <tt>@reflection</tt> object represents a <tt>:has_many</tt> macro.
  21. #
  22. # This class delegates unknown methods to <tt>@target</tt> via
  23. # <tt>method_missing</tt>.
  24. #
  25. # The <tt>@target</tt> object is not \loaded until needed. For example,
  26. #
  27. # blog.posts.count
  28. #
  29. # is computed directly through SQL and does not trigger by itself the
  30. # instantiation of the actual post records.
  31. 1 class CollectionProxy < Relation
  32. 1 def initialize(klass, association) #:nodoc:
  33. @association = association
  34. super klass
  35. extensions = association.extensions
  36. extend(*extensions) if extensions.any?
  37. end
  38. 1 def target
  39. @association.target
  40. end
  41. 1 def load_target
  42. @association.load_target
  43. end
  44. # Returns +true+ if the association has been loaded, otherwise +false+.
  45. #
  46. # person.pets.loaded? # => false
  47. # person.pets
  48. # person.pets.loaded? # => true
  49. 1 def loaded?
  50. @association.loaded?
  51. end
  52. ##
  53. # :method: select
  54. #
  55. # :call-seq:
  56. # select(*fields, &block)
  57. #
  58. # Works in two ways.
  59. #
  60. # *First:* Specify a subset of fields to be selected from the result set.
  61. #
  62. # class Person < ActiveRecord::Base
  63. # has_many :pets
  64. # end
  65. #
  66. # person.pets
  67. # # => [
  68. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  69. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  70. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  71. # # ]
  72. #
  73. # person.pets.select(:name)
  74. # # => [
  75. # # #<Pet id: nil, name: "Fancy-Fancy">,
  76. # # #<Pet id: nil, name: "Spook">,
  77. # # #<Pet id: nil, name: "Choo-Choo">
  78. # # ]
  79. #
  80. # person.pets.select(:id, :name)
  81. # # => [
  82. # # #<Pet id: 1, name: "Fancy-Fancy">,
  83. # # #<Pet id: 2, name: "Spook">,
  84. # # #<Pet id: 3, name: "Choo-Choo">
  85. # # ]
  86. #
  87. # Be careful because this also means you're initializing a model
  88. # object with only the fields that you've selected. If you attempt
  89. # to access a field except +id+ that is not in the initialized record you'll
  90. # receive:
  91. #
  92. # person.pets.select(:name).first.person_id
  93. # # => ActiveModel::MissingAttributeError: missing attribute: person_id
  94. #
  95. # *Second:* You can pass a block so it can be used just like Array#select.
  96. # This builds an array of objects from the database for the scope,
  97. # converting them into an array and iterating through them using
  98. # Array#select.
  99. #
  100. # person.pets.select { |pet| pet.name =~ /oo/ }
  101. # # => [
  102. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  103. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  104. # # ]
  105. # Finds an object in the collection responding to the +id+. Uses the same
  106. # rules as ActiveRecord::Base.find. Returns ActiveRecord::RecordNotFound
  107. # error if the object cannot be found.
  108. #
  109. # class Person < ActiveRecord::Base
  110. # has_many :pets
  111. # end
  112. #
  113. # person.pets
  114. # # => [
  115. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  116. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  117. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  118. # # ]
  119. #
  120. # person.pets.find(1) # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
  121. # person.pets.find(4) # => ActiveRecord::RecordNotFound: Couldn't find Pet with 'id'=4
  122. #
  123. # person.pets.find(2) { |pet| pet.name.downcase! }
  124. # # => #<Pet id: 2, name: "fancy-fancy", person_id: 1>
  125. #
  126. # person.pets.find(2, 3)
  127. # # => [
  128. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  129. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  130. # # ]
  131. 1 def find(*args)
  132. return super if block_given?
  133. @association.find(*args)
  134. end
  135. ##
  136. # :method: first
  137. #
  138. # :call-seq:
  139. # first(limit = nil)
  140. #
  141. # Returns the first record, or the first +n+ records, from the collection.
  142. # If the collection is empty, the first form returns +nil+, and the second
  143. # form returns an empty array.
  144. #
  145. # class Person < ActiveRecord::Base
  146. # has_many :pets
  147. # end
  148. #
  149. # person.pets
  150. # # => [
  151. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  152. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  153. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  154. # # ]
  155. #
  156. # person.pets.first # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
  157. #
  158. # person.pets.first(2)
  159. # # => [
  160. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  161. # # #<Pet id: 2, name: "Spook", person_id: 1>
  162. # # ]
  163. #
  164. # another_person_without.pets # => []
  165. # another_person_without.pets.first # => nil
  166. # another_person_without.pets.first(3) # => []
  167. ##
  168. # :method: second
  169. #
  170. # :call-seq:
  171. # second()
  172. #
  173. # Same as #first except returns only the second record.
  174. ##
  175. # :method: third
  176. #
  177. # :call-seq:
  178. # third()
  179. #
  180. # Same as #first except returns only the third record.
  181. ##
  182. # :method: fourth
  183. #
  184. # :call-seq:
  185. # fourth()
  186. #
  187. # Same as #first except returns only the fourth record.
  188. ##
  189. # :method: fifth
  190. #
  191. # :call-seq:
  192. # fifth()
  193. #
  194. # Same as #first except returns only the fifth record.
  195. ##
  196. # :method: forty_two
  197. #
  198. # :call-seq:
  199. # forty_two()
  200. #
  201. # Same as #first except returns only the forty second record.
  202. # Also known as accessing "the reddit".
  203. ##
  204. # :method: third_to_last
  205. #
  206. # :call-seq:
  207. # third_to_last()
  208. #
  209. # Same as #first except returns only the third-to-last record.
  210. ##
  211. # :method: second_to_last
  212. #
  213. # :call-seq:
  214. # second_to_last()
  215. #
  216. # Same as #first except returns only the second-to-last record.
  217. # Returns the last record, or the last +n+ records, from the collection.
  218. # If the collection is empty, the first form returns +nil+, and the second
  219. # form returns an empty array.
  220. #
  221. # class Person < ActiveRecord::Base
  222. # has_many :pets
  223. # end
  224. #
  225. # person.pets
  226. # # => [
  227. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  228. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  229. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  230. # # ]
  231. #
  232. # person.pets.last # => #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  233. #
  234. # person.pets.last(2)
  235. # # => [
  236. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  237. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  238. # # ]
  239. #
  240. # another_person_without.pets # => []
  241. # another_person_without.pets.last # => nil
  242. # another_person_without.pets.last(3) # => []
  243. 1 def last(limit = nil)
  244. load_target if find_from_target?
  245. super
  246. end
  247. # Gives a record (or N records if a parameter is supplied) from the collection
  248. # using the same rules as <tt>ActiveRecord::Base.take</tt>.
  249. #
  250. # class Person < ActiveRecord::Base
  251. # has_many :pets
  252. # end
  253. #
  254. # person.pets
  255. # # => [
  256. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  257. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  258. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  259. # # ]
  260. #
  261. # person.pets.take # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
  262. #
  263. # person.pets.take(2)
  264. # # => [
  265. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  266. # # #<Pet id: 2, name: "Spook", person_id: 1>
  267. # # ]
  268. #
  269. # another_person_without.pets # => []
  270. # another_person_without.pets.take # => nil
  271. # another_person_without.pets.take(2) # => []
  272. 1 def take(limit = nil)
  273. load_target if find_from_target?
  274. super
  275. end
  276. # Returns a new object of the collection type that has been instantiated
  277. # with +attributes+ and linked to this object, but have not yet been saved.
  278. # You can pass an array of attributes hashes, this will return an array
  279. # with the new objects.
  280. #
  281. # class Person
  282. # has_many :pets
  283. # end
  284. #
  285. # person.pets.build
  286. # # => #<Pet id: nil, name: nil, person_id: 1>
  287. #
  288. # person.pets.build(name: 'Fancy-Fancy')
  289. # # => #<Pet id: nil, name: "Fancy-Fancy", person_id: 1>
  290. #
  291. # person.pets.build([{name: 'Spook'}, {name: 'Choo-Choo'}, {name: 'Brain'}])
  292. # # => [
  293. # # #<Pet id: nil, name: "Spook", person_id: 1>,
  294. # # #<Pet id: nil, name: "Choo-Choo", person_id: 1>,
  295. # # #<Pet id: nil, name: "Brain", person_id: 1>
  296. # # ]
  297. #
  298. # person.pets.size # => 5 # size of the collection
  299. # person.pets.count # => 0 # count from database
  300. 1 def build(attributes = {}, &block)
  301. @association.build(attributes, &block)
  302. end
  303. 1 alias_method :new, :build
  304. # Returns a new object of the collection type that has been instantiated with
  305. # attributes, linked to this object and that has already been saved (if it
  306. # passes the validations).
  307. #
  308. # class Person
  309. # has_many :pets
  310. # end
  311. #
  312. # person.pets.create(name: 'Fancy-Fancy')
  313. # # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
  314. #
  315. # person.pets.create([{name: 'Spook'}, {name: 'Choo-Choo'}])
  316. # # => [
  317. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  318. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  319. # # ]
  320. #
  321. # person.pets.size # => 3
  322. # person.pets.count # => 3
  323. #
  324. # person.pets.find(1, 2, 3)
  325. # # => [
  326. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  327. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  328. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  329. # # ]
  330. 1 def create(attributes = {}, &block)
  331. @association.create(attributes, &block)
  332. end
  333. # Like #create, except that if the record is invalid, raises an exception.
  334. #
  335. # class Person
  336. # has_many :pets
  337. # end
  338. #
  339. # class Pet
  340. # validates :name, presence: true
  341. # end
  342. #
  343. # person.pets.create!(name: nil)
  344. # # => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
  345. 1 def create!(attributes = {}, &block)
  346. @association.create!(attributes, &block)
  347. end
  348. # Replaces this collection with +other_array+. This will perform a diff
  349. # and delete/add only records that have changed.
  350. #
  351. # class Person < ActiveRecord::Base
  352. # has_many :pets
  353. # end
  354. #
  355. # person.pets
  356. # # => [#<Pet id: 1, name: "Gorby", group: "cats", person_id: 1>]
  357. #
  358. # other_pets = [Pet.new(name: 'Puff', group: 'celebrities']
  359. #
  360. # person.pets.replace(other_pets)
  361. #
  362. # person.pets
  363. # # => [#<Pet id: 2, name: "Puff", group: "celebrities", person_id: 1>]
  364. #
  365. # If the supplied array has an incorrect association type, it raises
  366. # an <tt>ActiveRecord::AssociationTypeMismatch</tt> error:
  367. #
  368. # person.pets.replace(["doo", "ggie", "gaga"])
  369. # # => ActiveRecord::AssociationTypeMismatch: Pet expected, got String
  370. 1 def replace(other_array)
  371. @association.replace(other_array)
  372. end
  373. # Deletes all the records from the collection according to the strategy
  374. # specified by the +:dependent+ option. If no +:dependent+ option is given,
  375. # then it will follow the default strategy.
  376. #
  377. # For <tt>has_many :through</tt> associations, the default deletion strategy is
  378. # +:delete_all+.
  379. #
  380. # For +has_many+ associations, the default deletion strategy is +:nullify+.
  381. # This sets the foreign keys to +NULL+.
  382. #
  383. # class Person < ActiveRecord::Base
  384. # has_many :pets # dependent: :nullify option by default
  385. # end
  386. #
  387. # person.pets.size # => 3
  388. # person.pets
  389. # # => [
  390. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  391. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  392. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  393. # # ]
  394. #
  395. # person.pets.delete_all
  396. # # => [
  397. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  398. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  399. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  400. # # ]
  401. #
  402. # person.pets.size # => 0
  403. # person.pets # => []
  404. #
  405. # Pet.find(1, 2, 3)
  406. # # => [
  407. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: nil>,
  408. # # #<Pet id: 2, name: "Spook", person_id: nil>,
  409. # # #<Pet id: 3, name: "Choo-Choo", person_id: nil>
  410. # # ]
  411. #
  412. # Both +has_many+ and <tt>has_many :through</tt> dependencies default to the
  413. # +:delete_all+ strategy if the +:dependent+ option is set to +:destroy+.
  414. # Records are not instantiated and callbacks will not be fired.
  415. #
  416. # class Person < ActiveRecord::Base
  417. # has_many :pets, dependent: :destroy
  418. # end
  419. #
  420. # person.pets.size # => 3
  421. # person.pets
  422. # # => [
  423. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  424. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  425. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  426. # # ]
  427. #
  428. # person.pets.delete_all
  429. #
  430. # Pet.find(1, 2, 3)
  431. # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3)
  432. #
  433. # If it is set to <tt>:delete_all</tt>, all the objects are deleted
  434. # *without* calling their +destroy+ method.
  435. #
  436. # class Person < ActiveRecord::Base
  437. # has_many :pets, dependent: :delete_all
  438. # end
  439. #
  440. # person.pets.size # => 3
  441. # person.pets
  442. # # => [
  443. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  444. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  445. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  446. # # ]
  447. #
  448. # person.pets.delete_all
  449. #
  450. # Pet.find(1, 2, 3)
  451. # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3)
  452. 1 def delete_all(dependent = nil)
  453. @association.delete_all(dependent).tap { reset_scope }
  454. end
  455. # Deletes the records of the collection directly from the database
  456. # ignoring the +:dependent+ option. Records are instantiated and it
  457. # invokes +before_remove+, +after_remove+ , +before_destroy+ and
  458. # +after_destroy+ callbacks.
  459. #
  460. # class Person < ActiveRecord::Base
  461. # has_many :pets
  462. # end
  463. #
  464. # person.pets.size # => 3
  465. # person.pets
  466. # # => [
  467. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  468. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  469. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  470. # # ]
  471. #
  472. # person.pets.destroy_all
  473. #
  474. # person.pets.size # => 0
  475. # person.pets # => []
  476. #
  477. # Pet.find(1) # => Couldn't find Pet with id=1
  478. 1 def destroy_all
  479. @association.destroy_all.tap { reset_scope }
  480. end
  481. # Deletes the +records+ supplied from the collection according to the strategy
  482. # specified by the +:dependent+ option. If no +:dependent+ option is given,
  483. # then it will follow the default strategy. Returns an array with the
  484. # deleted records.
  485. #
  486. # For <tt>has_many :through</tt> associations, the default deletion strategy is
  487. # +:delete_all+.
  488. #
  489. # For +has_many+ associations, the default deletion strategy is +:nullify+.
  490. # This sets the foreign keys to +NULL+.
  491. #
  492. # class Person < ActiveRecord::Base
  493. # has_many :pets # dependent: :nullify option by default
  494. # end
  495. #
  496. # person.pets.size # => 3
  497. # person.pets
  498. # # => [
  499. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  500. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  501. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  502. # # ]
  503. #
  504. # person.pets.delete(Pet.find(1))
  505. # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
  506. #
  507. # person.pets.size # => 2
  508. # person.pets
  509. # # => [
  510. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  511. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  512. # # ]
  513. #
  514. # Pet.find(1)
  515. # # => #<Pet id: 1, name: "Fancy-Fancy", person_id: nil>
  516. #
  517. # If it is set to <tt>:destroy</tt> all the +records+ are removed by calling
  518. # their +destroy+ method. See +destroy+ for more information.
  519. #
  520. # class Person < ActiveRecord::Base
  521. # has_many :pets, dependent: :destroy
  522. # end
  523. #
  524. # person.pets.size # => 3
  525. # person.pets
  526. # # => [
  527. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  528. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  529. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  530. # # ]
  531. #
  532. # person.pets.delete(Pet.find(1), Pet.find(3))
  533. # # => [
  534. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  535. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  536. # # ]
  537. #
  538. # person.pets.size # => 1
  539. # person.pets
  540. # # => [#<Pet id: 2, name: "Spook", person_id: 1>]
  541. #
  542. # Pet.find(1, 3)
  543. # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 3)
  544. #
  545. # If it is set to <tt>:delete_all</tt>, all the +records+ are deleted
  546. # *without* calling their +destroy+ method.
  547. #
  548. # class Person < ActiveRecord::Base
  549. # has_many :pets, dependent: :delete_all
  550. # end
  551. #
  552. # person.pets.size # => 3
  553. # person.pets
  554. # # => [
  555. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  556. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  557. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  558. # # ]
  559. #
  560. # person.pets.delete(Pet.find(1))
  561. # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
  562. #
  563. # person.pets.size # => 2
  564. # person.pets
  565. # # => [
  566. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  567. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  568. # # ]
  569. #
  570. # Pet.find(1)
  571. # # => ActiveRecord::RecordNotFound: Couldn't find Pet with 'id'=1
  572. #
  573. # You can pass +Integer+ or +String+ values, it finds the records
  574. # responding to the +id+ and executes delete on them.
  575. #
  576. # class Person < ActiveRecord::Base
  577. # has_many :pets
  578. # end
  579. #
  580. # person.pets.size # => 3
  581. # person.pets
  582. # # => [
  583. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  584. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  585. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  586. # # ]
  587. #
  588. # person.pets.delete("1")
  589. # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
  590. #
  591. # person.pets.delete(2, 3)
  592. # # => [
  593. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  594. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  595. # # ]
  596. 1 def delete(*records)
  597. @association.delete(*records).tap { reset_scope }
  598. end
  599. # Destroys the +records+ supplied and removes them from the collection.
  600. # This method will _always_ remove record from the database ignoring
  601. # the +:dependent+ option. Returns an array with the removed records.
  602. #
  603. # class Person < ActiveRecord::Base
  604. # has_many :pets
  605. # end
  606. #
  607. # person.pets.size # => 3
  608. # person.pets
  609. # # => [
  610. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  611. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  612. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  613. # # ]
  614. #
  615. # person.pets.destroy(Pet.find(1))
  616. # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
  617. #
  618. # person.pets.size # => 2
  619. # person.pets
  620. # # => [
  621. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  622. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  623. # # ]
  624. #
  625. # person.pets.destroy(Pet.find(2), Pet.find(3))
  626. # # => [
  627. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  628. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  629. # # ]
  630. #
  631. # person.pets.size # => 0
  632. # person.pets # => []
  633. #
  634. # Pet.find(1, 2, 3) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3)
  635. #
  636. # You can pass +Integer+ or +String+ values, it finds the records
  637. # responding to the +id+ and then deletes them from the database.
  638. #
  639. # person.pets.size # => 3
  640. # person.pets
  641. # # => [
  642. # # #<Pet id: 4, name: "Benny", person_id: 1>,
  643. # # #<Pet id: 5, name: "Brain", person_id: 1>,
  644. # # #<Pet id: 6, name: "Boss", person_id: 1>
  645. # # ]
  646. #
  647. # person.pets.destroy("4")
  648. # # => #<Pet id: 4, name: "Benny", person_id: 1>
  649. #
  650. # person.pets.size # => 2
  651. # person.pets
  652. # # => [
  653. # # #<Pet id: 5, name: "Brain", person_id: 1>,
  654. # # #<Pet id: 6, name: "Boss", person_id: 1>
  655. # # ]
  656. #
  657. # person.pets.destroy(5, 6)
  658. # # => [
  659. # # #<Pet id: 5, name: "Brain", person_id: 1>,
  660. # # #<Pet id: 6, name: "Boss", person_id: 1>
  661. # # ]
  662. #
  663. # person.pets.size # => 0
  664. # person.pets # => []
  665. #
  666. # Pet.find(4, 5, 6) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (4, 5, 6)
  667. 1 def destroy(*records)
  668. @association.destroy(*records).tap { reset_scope }
  669. end
  670. ##
  671. # :method: distinct
  672. #
  673. # :call-seq:
  674. # distinct(value = true)
  675. #
  676. # Specifies whether the records should be unique or not.
  677. #
  678. # class Person < ActiveRecord::Base
  679. # has_many :pets
  680. # end
  681. #
  682. # person.pets.select(:name)
  683. # # => [
  684. # # #<Pet name: "Fancy-Fancy">,
  685. # # #<Pet name: "Fancy-Fancy">
  686. # # ]
  687. #
  688. # person.pets.select(:name).distinct
  689. # # => [#<Pet name: "Fancy-Fancy">]
  690. #
  691. # person.pets.select(:name).distinct.distinct(false)
  692. # # => [
  693. # # #<Pet name: "Fancy-Fancy">,
  694. # # #<Pet name: "Fancy-Fancy">
  695. # # ]
  696. #--
  697. 1 def calculate(operation, column_name)
  698. null_scope? ? scope.calculate(operation, column_name) : super
  699. end
  700. 1 def pluck(*column_names)
  701. null_scope? ? scope.pluck(*column_names) : super
  702. end
  703. ##
  704. # :method: count
  705. #
  706. # :call-seq:
  707. # count(column_name = nil, &block)
  708. #
  709. # Count all records.
  710. #
  711. # class Person < ActiveRecord::Base
  712. # has_many :pets
  713. # end
  714. #
  715. # # This will perform the count using SQL.
  716. # person.pets.count # => 3
  717. # person.pets
  718. # # => [
  719. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  720. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  721. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  722. # # ]
  723. #
  724. # Passing a block will select all of a person's pets in SQL and then
  725. # perform the count using Ruby.
  726. #
  727. # person.pets.count { |pet| pet.name.include?('-') } # => 2
  728. # Returns the size of the collection. If the collection hasn't been loaded,
  729. # it executes a <tt>SELECT COUNT(*)</tt> query. Else it calls <tt>collection.size</tt>.
  730. #
  731. # If the collection has been already loaded +size+ and +length+ are
  732. # equivalent. If not and you are going to need the records anyway
  733. # +length+ will take one less query. Otherwise +size+ is more efficient.
  734. #
  735. # class Person < ActiveRecord::Base
  736. # has_many :pets
  737. # end
  738. #
  739. # person.pets.size # => 3
  740. # # executes something like SELECT COUNT(*) FROM "pets" WHERE "pets"."person_id" = 1
  741. #
  742. # person.pets # This will execute a SELECT * FROM query
  743. # # => [
  744. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  745. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  746. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  747. # # ]
  748. #
  749. # person.pets.size # => 3
  750. # # Because the collection is already loaded, this will behave like
  751. # # collection.size and no SQL count query is executed.
  752. 1 def size
  753. @association.size
  754. end
  755. ##
  756. # :method: length
  757. #
  758. # :call-seq:
  759. # length()
  760. #
  761. # Returns the size of the collection calling +size+ on the target.
  762. # If the collection has been already loaded, +length+ and +size+ are
  763. # equivalent. If not and you are going to need the records anyway this
  764. # method will take one less query. Otherwise +size+ is more efficient.
  765. #
  766. # class Person < ActiveRecord::Base
  767. # has_many :pets
  768. # end
  769. #
  770. # person.pets.length # => 3
  771. # # executes something like SELECT "pets".* FROM "pets" WHERE "pets"."person_id" = 1
  772. #
  773. # # Because the collection is loaded, you can
  774. # # call the collection with no additional queries:
  775. # person.pets
  776. # # => [
  777. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  778. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  779. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  780. # # ]
  781. # Returns +true+ if the collection is empty. If the collection has been
  782. # loaded it is equivalent
  783. # to <tt>collection.size.zero?</tt>. If the collection has not been loaded,
  784. # it is equivalent to <tt>!collection.exists?</tt>. If the collection has
  785. # not already been loaded and you are going to fetch the records anyway it
  786. # is better to check <tt>collection.length.zero?</tt>.
  787. #
  788. # class Person < ActiveRecord::Base
  789. # has_many :pets
  790. # end
  791. #
  792. # person.pets.count # => 1
  793. # person.pets.empty? # => false
  794. #
  795. # person.pets.delete_all
  796. #
  797. # person.pets.count # => 0
  798. # person.pets.empty? # => true
  799. 1 def empty?
  800. @association.empty?
  801. end
  802. ##
  803. # :method: any?
  804. #
  805. # :call-seq:
  806. # any?()
  807. #
  808. # Returns +true+ if the collection is not empty.
  809. #
  810. # class Person < ActiveRecord::Base
  811. # has_many :pets
  812. # end
  813. #
  814. # person.pets.count # => 0
  815. # person.pets.any? # => false
  816. #
  817. # person.pets << Pet.new(name: 'Snoop')
  818. # person.pets.count # => 1
  819. # person.pets.any? # => true
  820. #
  821. # You can also pass a +block+ to define criteria. The behavior
  822. # is the same, it returns true if the collection based on the
  823. # criteria is not empty.
  824. #
  825. # person.pets
  826. # # => [#<Pet name: "Snoop", group: "dogs">]
  827. #
  828. # person.pets.any? do |pet|
  829. # pet.group == 'cats'
  830. # end
  831. # # => false
  832. #
  833. # person.pets.any? do |pet|
  834. # pet.group == 'dogs'
  835. # end
  836. # # => true
  837. ##
  838. # :method: many?
  839. #
  840. # :call-seq:
  841. # many?()
  842. #
  843. # Returns true if the collection has more than one record.
  844. # Equivalent to <tt>collection.size > 1</tt>.
  845. #
  846. # class Person < ActiveRecord::Base
  847. # has_many :pets
  848. # end
  849. #
  850. # person.pets.count # => 1
  851. # person.pets.many? # => false
  852. #
  853. # person.pets << Pet.new(name: 'Snoopy')
  854. # person.pets.count # => 2
  855. # person.pets.many? # => true
  856. #
  857. # You can also pass a +block+ to define criteria. The
  858. # behavior is the same, it returns true if the collection
  859. # based on the criteria has more than one record.
  860. #
  861. # person.pets
  862. # # => [
  863. # # #<Pet name: "Gorby", group: "cats">,
  864. # # #<Pet name: "Puff", group: "cats">,
  865. # # #<Pet name: "Snoop", group: "dogs">
  866. # # ]
  867. #
  868. # person.pets.many? do |pet|
  869. # pet.group == 'dogs'
  870. # end
  871. # # => false
  872. #
  873. # person.pets.many? do |pet|
  874. # pet.group == 'cats'
  875. # end
  876. # # => true
  877. # Returns +true+ if the given +record+ is present in the collection.
  878. #
  879. # class Person < ActiveRecord::Base
  880. # has_many :pets
  881. # end
  882. #
  883. # person.pets # => [#<Pet id: 20, name: "Snoop">]
  884. #
  885. # person.pets.include?(Pet.find(20)) # => true
  886. # person.pets.include?(Pet.find(21)) # => false
  887. 1 def include?(record)
  888. !!@association.include?(record)
  889. end
  890. 1 def proxy_association
  891. @association
  892. end
  893. # Returns a <tt>Relation</tt> object for the records in this association
  894. 1 def scope
  895. @scope ||= @association.scope
  896. end
  897. # Equivalent to <tt>Array#==</tt>. Returns +true+ if the two arrays
  898. # contain the same number of elements and if each element is equal
  899. # to the corresponding element in the +other+ array, otherwise returns
  900. # +false+.
  901. #
  902. # class Person < ActiveRecord::Base
  903. # has_many :pets
  904. # end
  905. #
  906. # person.pets
  907. # # => [
  908. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  909. # # #<Pet id: 2, name: "Spook", person_id: 1>
  910. # # ]
  911. #
  912. # other = person.pets.to_ary
  913. #
  914. # person.pets == other
  915. # # => true
  916. #
  917. # other = [Pet.new(id: 1), Pet.new(id: 2)]
  918. #
  919. # person.pets == other
  920. # # => false
  921. 1 def ==(other)
  922. load_target == other
  923. end
  924. ##
  925. # :method: to_ary
  926. #
  927. # :call-seq:
  928. # to_ary()
  929. #
  930. # Returns a new array of objects from the collection. If the collection
  931. # hasn't been loaded, it fetches the records from the database.
  932. #
  933. # class Person < ActiveRecord::Base
  934. # has_many :pets
  935. # end
  936. #
  937. # person.pets
  938. # # => [
  939. # # #<Pet id: 4, name: "Benny", person_id: 1>,
  940. # # #<Pet id: 5, name: "Brain", person_id: 1>,
  941. # # #<Pet id: 6, name: "Boss", person_id: 1>
  942. # # ]
  943. #
  944. # other_pets = person.pets.to_ary
  945. # # => [
  946. # # #<Pet id: 4, name: "Benny", person_id: 1>,
  947. # # #<Pet id: 5, name: "Brain", person_id: 1>,
  948. # # #<Pet id: 6, name: "Boss", person_id: 1>
  949. # # ]
  950. #
  951. # other_pets.replace([Pet.new(name: 'BooGoo')])
  952. #
  953. # other_pets
  954. # # => [#<Pet id: nil, name: "BooGoo", person_id: 1>]
  955. #
  956. # person.pets
  957. # # This is not affected by replace
  958. # # => [
  959. # # #<Pet id: 4, name: "Benny", person_id: 1>,
  960. # # #<Pet id: 5, name: "Brain", person_id: 1>,
  961. # # #<Pet id: 6, name: "Boss", person_id: 1>
  962. # # ]
  963. 1 def records # :nodoc:
  964. load_target
  965. end
  966. # Adds one or more +records+ to the collection by setting their foreign keys
  967. # to the association's primary key. Since +<<+ flattens its argument list and
  968. # inserts each record, +push+ and +concat+ behave identically. Returns +self+
  969. # so several appends may be chained together.
  970. #
  971. # class Person < ActiveRecord::Base
  972. # has_many :pets
  973. # end
  974. #
  975. # person.pets.size # => 0
  976. # person.pets << Pet.new(name: 'Fancy-Fancy')
  977. # person.pets << [Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo')]
  978. # person.pets.size # => 3
  979. #
  980. # person.id # => 1
  981. # person.pets
  982. # # => [
  983. # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
  984. # # #<Pet id: 2, name: "Spook", person_id: 1>,
  985. # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
  986. # # ]
  987. 1 def <<(*records)
  988. proxy_association.concat(records) && self
  989. end
  990. 1 alias_method :push, :<<
  991. 1 alias_method :append, :<<
  992. 1 alias_method :concat, :<<
  993. 1 def prepend(*args)
  994. raise NoMethodError, "prepend on association is not defined. Please use <<, push or append"
  995. end
  996. # Equivalent to +delete_all+. The difference is that returns +self+, instead
  997. # of an array with the deleted objects, so methods can be chained. See
  998. # +delete_all+ for more information.
  999. # Note that because +delete_all+ removes records by directly
  1000. # running an SQL query into the database, the +updated_at+ column of
  1001. # the object is not changed.
  1002. 1 def clear
  1003. delete_all
  1004. self
  1005. end
  1006. # Reloads the collection from the database. Returns +self+.
  1007. #
  1008. # class Person < ActiveRecord::Base
  1009. # has_many :pets
  1010. # end
  1011. #
  1012. # person.pets # fetches pets from the database
  1013. # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
  1014. #
  1015. # person.pets # uses the pets cache
  1016. # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
  1017. #
  1018. # person.pets.reload # fetches pets from the database
  1019. # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
  1020. 1 def reload
  1021. proxy_association.reload
  1022. reset_scope
  1023. end
  1024. # Unloads the association. Returns +self+.
  1025. #
  1026. # class Person < ActiveRecord::Base
  1027. # has_many :pets
  1028. # end
  1029. #
  1030. # person.pets # fetches pets from the database
  1031. # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
  1032. #
  1033. # person.pets # uses the pets cache
  1034. # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
  1035. #
  1036. # person.pets.reset # clears the pets cache
  1037. #
  1038. # person.pets # fetches pets from the database
  1039. # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
  1040. 1 def reset
  1041. proxy_association.reset
  1042. proxy_association.reset_scope
  1043. reset_scope
  1044. end
  1045. 1 def reset_scope # :nodoc:
  1046. @offsets = {}
  1047. @scope = nil
  1048. self
  1049. end
  1050. delegate_methods = [
  1051. 1 QueryMethods,
  1052. SpawnMethods,
  1053. ].flat_map { |klass|
  1054. 2 klass.public_instance_methods(false)
  1055. } - self.public_instance_methods(false) - [:select] + [:scoping]
  1056. 1 delegate(*delegate_methods, to: :scope)
  1057. 1 private
  1058. 1 def find_nth_with_limit(index, limit)
  1059. load_target if find_from_target?
  1060. super
  1061. end
  1062. 1 def find_nth_from_last(index)
  1063. load_target if find_from_target?
  1064. super
  1065. end
  1066. 1 def null_scope?
  1067. @association.null_scope?
  1068. end
  1069. 1 def find_from_target?
  1070. @association.find_from_target?
  1071. end
  1072. 1 def exec_queries
  1073. load_target
  1074. end
  1075. end
  1076. end
  1077. end

target/rubygems/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/transaction.rb

55.77% lines covered

156 relevant lines. 87 lines covered and 69 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveRecord
  3. 1 module ConnectionAdapters
  4. 1 class TransactionState
  5. 1 def initialize(state = nil)
  6. 2 @state = state
  7. 2 @children = []
  8. end
  9. 1 def add_child(state)
  10. @children << state
  11. end
  12. 1 def finalized?
  13. @state
  14. end
  15. 1 def committed?
  16. @state == :committed || @state == :fully_committed
  17. end
  18. 1 def fully_committed?
  19. @state == :fully_committed
  20. end
  21. 1 def rolledback?
  22. @state == :rolledback || @state == :fully_rolledback
  23. end
  24. 1 def fully_rolledback?
  25. @state == :fully_rolledback
  26. end
  27. 1 def fully_completed?
  28. completed?
  29. end
  30. 1 def completed?
  31. committed? || rolledback?
  32. end
  33. 1 def set_state(state)
  34. ActiveSupport::Deprecation.warn(<<-MSG.squish)
  35. The set_state method is deprecated and will be removed in
  36. Rails 6.0. Please use rollback! or commit! to set transaction
  37. state directly.
  38. MSG
  39. case state
  40. when :rolledback
  41. rollback!
  42. when :committed
  43. commit!
  44. when nil
  45. nullify!
  46. else
  47. raise ArgumentError, "Invalid transaction state: #{state}"
  48. end
  49. end
  50. 1 def rollback!
  51. @children.each { |c| c.rollback! }
  52. @state = :rolledback
  53. end
  54. 1 def full_rollback!
  55. 2 @children.each { |c| c.rollback! }
  56. 2 @state = :fully_rolledback
  57. end
  58. 1 def commit!
  59. @state = :committed
  60. end
  61. 1 def full_commit!
  62. @state = :fully_committed
  63. end
  64. 1 def nullify!
  65. @state = nil
  66. end
  67. end
  68. 1 class NullTransaction #:nodoc:
  69. 1 def initialize; end
  70. 1 def state; end
  71. 1 def closed?; true; end
  72. 1 def open?; false; end
  73. 3 def joinable?; false; end
  74. 1 def add_record(record); end
  75. end
  76. 1 class Transaction #:nodoc:
  77. 1 attr_reader :connection, :state, :records, :savepoint_name
  78. 1 attr_writer :joinable
  79. 1 def initialize(connection, options, run_commit_callbacks: false)
  80. 2 @connection = connection
  81. 2 @state = TransactionState.new
  82. 2 @records = []
  83. 2 @joinable = options.fetch(:joinable, true)
  84. 2 @run_commit_callbacks = run_commit_callbacks
  85. end
  86. 1 def add_record(record)
  87. records << record
  88. end
  89. 1 def rollback_records
  90. 2 ite = records.uniq
  91. 2 while record = ite.shift
  92. record.rolledback!(force_restore_state: full_rollback?)
  93. end
  94. ensure
  95. 2 ite.each do |i|
  96. i.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: false)
  97. end
  98. end
  99. 1 def before_commit_records
  100. records.uniq.each(&:before_committed!) if @run_commit_callbacks
  101. end
  102. 1 def commit_records
  103. ite = records.uniq
  104. while record = ite.shift
  105. if @run_commit_callbacks
  106. record.committed!
  107. else
  108. # if not running callbacks, only adds the record to the parent transaction
  109. record.add_to_transaction
  110. end
  111. end
  112. ensure
  113. ite.each { |i| i.committed!(should_run_callbacks: false) }
  114. end
  115. 1 def full_rollback?; true; end
  116. 1 def joinable?; @joinable; end
  117. 5 def closed?; false; end
  118. 5 def open?; !closed?; end
  119. end
  120. 1 class SavepointTransaction < Transaction
  121. 1 def initialize(connection, savepoint_name, parent_transaction, options, *args)
  122. super(connection, options, *args)
  123. parent_transaction.state.add_child(@state)
  124. if options[:isolation]
  125. raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction"
  126. end
  127. connection.create_savepoint(@savepoint_name = savepoint_name)
  128. end
  129. 1 def rollback
  130. connection.rollback_to_savepoint(savepoint_name)
  131. @state.rollback!
  132. end
  133. 1 def commit
  134. connection.release_savepoint(savepoint_name)
  135. @state.commit!
  136. end
  137. 1 def full_rollback?; false; end
  138. end
  139. 1 class RealTransaction < Transaction
  140. 1 def initialize(connection, options, *args)
  141. 2 super
  142. 2 if options[:isolation]
  143. connection.begin_isolated_db_transaction(options[:isolation])
  144. else
  145. 2 connection.begin_db_transaction
  146. end
  147. end
  148. 1 def rollback
  149. 2 connection.rollback_db_transaction
  150. 2 @state.full_rollback!
  151. end
  152. 1 def commit
  153. connection.commit_db_transaction
  154. @state.full_commit!
  155. end
  156. end
  157. 1 class TransactionManager #:nodoc:
  158. 1 def initialize(connection)
  159. 1 @stack = []
  160. 1 @connection = connection
  161. end
  162. 1 def begin_transaction(options = {})
  163. 2 @connection.lock.synchronize do
  164. 2 run_commit_callbacks = !current_transaction.joinable?
  165. transaction =
  166. 1 if @stack.empty?
  167. 2 RealTransaction.new(@connection, options, run_commit_callbacks: run_commit_callbacks)
  168. else
  169. SavepointTransaction.new(@connection, "active_record_#{@stack.size}", @stack.last, options,
  170. run_commit_callbacks: run_commit_callbacks)
  171. end
  172. 2 @stack.push(transaction)
  173. 2 transaction
  174. end
  175. end
  176. 1 def commit_transaction
  177. @connection.lock.synchronize do
  178. transaction = @stack.last
  179. begin
  180. transaction.before_commit_records
  181. ensure
  182. @stack.pop
  183. end
  184. transaction.commit
  185. transaction.commit_records
  186. end
  187. end
  188. 1 def rollback_transaction(transaction = nil)
  189. 2 @connection.lock.synchronize do
  190. 2 transaction ||= @stack.pop
  191. 2 transaction.rollback
  192. 2 transaction.rollback_records
  193. end
  194. end
  195. 1 def within_new_transaction(options = {})
  196. @connection.lock.synchronize do
  197. begin
  198. transaction = begin_transaction options
  199. yield
  200. rescue Exception => error
  201. if transaction
  202. rollback_transaction
  203. after_failure_actions(transaction, error)
  204. end
  205. raise
  206. ensure
  207. unless error
  208. if Thread.current.status == "aborting"
  209. rollback_transaction if transaction
  210. else
  211. begin
  212. commit_transaction if transaction
  213. rescue Exception
  214. rollback_transaction(transaction) unless transaction.state.completed?
  215. raise
  216. end
  217. end
  218. end
  219. end
  220. end
  221. end
  222. 1 def open_transactions
  223. @stack.size
  224. end
  225. 1 def current_transaction
  226. 6 @stack.last || NULL_TRANSACTION
  227. end
  228. 1 private
  229. 1 NULL_TRANSACTION = NullTransaction.new
  230. # Deallocate invalidated prepared statements outside of the transaction
  231. 1 def after_failure_actions(transaction, error)
  232. return unless transaction.is_a?(RealTransaction)
  233. return unless error.is_a?(ActiveRecord::PreparedStatementCacheExpired)
  234. @connection.clear_cache!
  235. end
  236. end
  237. end
  238. end

target/rubygems/gems/activerecord-5.2.3/lib/active_record/fixture_set/file.rb

39.53% lines covered

43 relevant lines. 17 lines covered and 26 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "erb"
  3. 1 require "yaml"
  4. 1 module ActiveRecord
  5. 1 class FixtureSet
  6. 1 class File # :nodoc:
  7. 1 include Enumerable
  8. ##
  9. # Open a fixture file named +file+. When called with a block, the block
  10. # is called with the filehandle and the filehandle is automatically closed
  11. # when the block finishes.
  12. 1 def self.open(file)
  13. x = new file
  14. block_given? ? yield(x) : x
  15. end
  16. 1 def initialize(file)
  17. @file = file
  18. end
  19. 1 def each(&block)
  20. rows.each(&block)
  21. end
  22. 1 def model_class
  23. config_row["model_class"]
  24. end
  25. 1 private
  26. 1 def rows
  27. @rows ||= raw_rows.reject { |fixture_name, _| fixture_name == "_fixture" }
  28. end
  29. 1 def config_row
  30. @config_row ||= begin
  31. row = raw_rows.find { |fixture_name, _| fixture_name == "_fixture" }
  32. if row
  33. row.last
  34. else
  35. { 'model_class': nil }
  36. end
  37. end
  38. end
  39. 1 def raw_rows
  40. @raw_rows ||= begin
  41. data = YAML.load(render(IO.read(@file)))
  42. data ? validate(data).to_a : []
  43. rescue ArgumentError, Psych::SyntaxError => error
  44. raise Fixture::FormatError, "a YAML error occurred parsing #{@file}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{error.class}: #{error}", error.backtrace
  45. end
  46. end
  47. 1 def prepare_erb(content)
  48. erb = ERB.new(content)
  49. erb.filename = @file
  50. erb
  51. end
  52. 1 def render(content)
  53. context = ActiveRecord::FixtureSet::RenderContext.create_subclass.new
  54. prepare_erb(content).result(context.get_binding)
  55. end
  56. # Validate our unmarshalled data.
  57. 1 def validate(data)
  58. unless Hash === data || YAML::Omap === data
  59. raise Fixture::FormatError, "fixture is not a hash: #{@file}"
  60. end
  61. invalid = data.reject { |_, row| Hash === row }
  62. if invalid.any?
  63. raise Fixture::FormatError, "fixture key is not a hash: #{@file}, keys: #{invalid.keys.inspect}"
  64. end
  65. data
  66. end
  67. end
  68. end
  69. end

target/rubygems/gems/activerecord-5.2.3/lib/active_record/fixtures.rb

44.51% lines covered

328 relevant lines. 146 lines covered and 182 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "erb"
  3. 1 require "yaml"
  4. 1 require "zlib"
  5. 1 require "set"
  6. 1 require "active_support/dependencies"
  7. 1 require "active_support/core_ext/digest/uuid"
  8. 1 require "active_record/fixture_set/file"
  9. 1 require "active_record/errors"
  10. 1 module ActiveRecord
  11. 1 class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc:
  12. end
  13. # \Fixtures are a way of organizing data that you want to test against; in short, sample data.
  14. #
  15. # They are stored in YAML files, one file per model, which are placed in the directory
  16. # appointed by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is automatically
  17. # configured for Rails, so you can just put your files in <tt><your-rails-app>/test/fixtures/</tt>).
  18. # The fixture file ends with the +.yml+ file extension, for example:
  19. # <tt><your-rails-app>/test/fixtures/web_sites.yml</tt>).
  20. #
  21. # The format of a fixture file looks like this:
  22. #
  23. # rubyonrails:
  24. # id: 1
  25. # name: Ruby on Rails
  26. # url: http://www.rubyonrails.org
  27. #
  28. # google:
  29. # id: 2
  30. # name: Google
  31. # url: http://www.google.com
  32. #
  33. # This fixture file includes two fixtures. Each YAML fixture (ie. record) is given a name and
  34. # is followed by an indented list of key/value pairs in the "key: value" format. Records are
  35. # separated by a blank line for your viewing pleasure.
  36. #
  37. # Note: Fixtures are unordered. If you want ordered fixtures, use the omap YAML type.
  38. # See http://yaml.org/type/omap.html
  39. # for the specification. You will need ordered fixtures when you have foreign key constraints
  40. # on keys in the same table. This is commonly needed for tree structures. Example:
  41. #
  42. # --- !omap
  43. # - parent:
  44. # id: 1
  45. # parent_id: NULL
  46. # title: Parent
  47. # - child:
  48. # id: 2
  49. # parent_id: 1
  50. # title: Child
  51. #
  52. # = Using Fixtures in Test Cases
  53. #
  54. # Since fixtures are a testing construct, we use them in our unit and functional tests. There
  55. # are two ways to use the fixtures, but first let's take a look at a sample unit test:
  56. #
  57. # require 'test_helper'
  58. #
  59. # class WebSiteTest < ActiveSupport::TestCase
  60. # test "web_site_count" do
  61. # assert_equal 2, WebSite.count
  62. # end
  63. # end
  64. #
  65. # By default, +test_helper.rb+ will load all of your fixtures into your test
  66. # database, so this test will succeed.
  67. #
  68. # The testing environment will automatically load all the fixtures into the database before each
  69. # test. To ensure consistent data, the environment deletes the fixtures before running the load.
  70. #
  71. # In addition to being available in the database, the fixture's data may also be accessed by
  72. # using a special dynamic method, which has the same name as the model.
  73. #
  74. # Passing in a fixture name to this dynamic method returns the fixture matching this name:
  75. #
  76. # test "find one" do
  77. # assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
  78. # end
  79. #
  80. # Passing in multiple fixture names returns all fixtures matching these names:
  81. #
  82. # test "find all by name" do
  83. # assert_equal 2, web_sites(:rubyonrails, :google).length
  84. # end
  85. #
  86. # Passing in no arguments returns all fixtures:
  87. #
  88. # test "find all" do
  89. # assert_equal 2, web_sites.length
  90. # end
  91. #
  92. # Passing in any fixture name that does not exist will raise <tt>StandardError</tt>:
  93. #
  94. # test "find by name that does not exist" do
  95. # assert_raise(StandardError) { web_sites(:reddit) }
  96. # end
  97. #
  98. # Alternatively, you may enable auto-instantiation of the fixture data. For instance, take the
  99. # following tests:
  100. #
  101. # test "find_alt_method_1" do
  102. # assert_equal "Ruby on Rails", @web_sites['rubyonrails']['name']
  103. # end
  104. #
  105. # test "find_alt_method_2" do
  106. # assert_equal "Ruby on Rails", @rubyonrails.name
  107. # end
  108. #
  109. # In order to use these methods to access fixtured data within your test cases, you must specify one of the
  110. # following in your ActiveSupport::TestCase-derived class:
  111. #
  112. # - to fully enable instantiated fixtures (enable alternate methods #1 and #2 above)
  113. # self.use_instantiated_fixtures = true
  114. #
  115. # - create only the hash for the fixtures, do not 'find' each instance (enable alternate method #1 only)
  116. # self.use_instantiated_fixtures = :no_instances
  117. #
  118. # Using either of these alternate methods incurs a performance hit, as the fixtured data must be fully
  119. # traversed in the database to create the fixture hash and/or instance variables. This is expensive for
  120. # large sets of fixtured data.
  121. #
  122. # = Dynamic fixtures with ERB
  123. #
  124. # Sometimes you don't care about the content of the fixtures as much as you care about the volume.
  125. # In these cases, you can mix ERB in with your YAML fixtures to create a bunch of fixtures for load
  126. # testing, like:
  127. #
  128. # <% 1.upto(1000) do |i| %>
  129. # fix_<%= i %>:
  130. # id: <%= i %>
  131. # name: guy_<%= i %>
  132. # <% end %>
  133. #
  134. # This will create 1000 very simple fixtures.
  135. #
  136. # Using ERB, you can also inject dynamic values into your fixtures with inserts like
  137. # <tt><%= Date.today.strftime("%Y-%m-%d") %></tt>.
  138. # This is however a feature to be used with some caution. The point of fixtures are that they're
  139. # stable units of predictable sample data. If you feel that you need to inject dynamic values, then
  140. # perhaps you should reexamine whether your application is properly testable. Hence, dynamic values
  141. # in fixtures are to be considered a code smell.
  142. #
  143. # Helper methods defined in a fixture will not be available in other fixtures, to prevent against
  144. # unwanted inter-test dependencies. Methods used by multiple fixtures should be defined in a module
  145. # that is included in ActiveRecord::FixtureSet.context_class.
  146. #
  147. # - define a helper method in <tt>test_helper.rb</tt>
  148. # module FixtureFileHelpers
  149. # def file_sha(path)
  150. # Digest::SHA2.hexdigest(File.read(Rails.root.join('test/fixtures', path)))
  151. # end
  152. # end
  153. # ActiveRecord::FixtureSet.context_class.include FixtureFileHelpers
  154. #
  155. # - use the helper method in a fixture
  156. # photo:
  157. # name: kitten.png
  158. # sha: <%= file_sha 'files/kitten.png' %>
  159. #
  160. # = Transactional Tests
  161. #
  162. # Test cases can use begin+rollback to isolate their changes to the database instead of having to
  163. # delete+insert for every test case.
  164. #
  165. # class FooTest < ActiveSupport::TestCase
  166. # self.use_transactional_tests = true
  167. #
  168. # test "godzilla" do
  169. # assert_not_empty Foo.all
  170. # Foo.destroy_all
  171. # assert_empty Foo.all
  172. # end
  173. #
  174. # test "godzilla aftermath" do
  175. # assert_not_empty Foo.all
  176. # end
  177. # end
  178. #
  179. # If you preload your test database with all fixture data (probably in the rake task) and use
  180. # transactional tests, then you may omit all fixtures declarations in your test cases since
  181. # all the data's already there and every case rolls back its changes.
  182. #
  183. # In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to
  184. # true. This will provide access to fixture data for every table that has been loaded through
  185. # fixtures (depending on the value of +use_instantiated_fixtures+).
  186. #
  187. # When *not* to use transactional tests:
  188. #
  189. # 1. You're testing whether a transaction works correctly. Nested transactions don't commit until
  190. # all parent transactions commit, particularly, the fixtures transaction which is begun in setup
  191. # and rolled back in teardown. Thus, you won't be able to verify
  192. # the results of your transaction until Active Record supports nested transactions or savepoints (in progress).
  193. # 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM.
  194. # Use InnoDB, MaxDB, or NDB instead.
  195. #
  196. # = Advanced Fixtures
  197. #
  198. # Fixtures that don't specify an ID get some extra features:
  199. #
  200. # * Stable, autogenerated IDs
  201. # * Label references for associations (belongs_to, has_one, has_many)
  202. # * HABTM associations as inline lists
  203. #
  204. # There are some more advanced features available even if the id is specified:
  205. #
  206. # * Autofilled timestamp columns
  207. # * Fixture label interpolation
  208. # * Support for YAML defaults
  209. #
  210. # == Stable, Autogenerated IDs
  211. #
  212. # Here, have a monkey fixture:
  213. #
  214. # george:
  215. # id: 1
  216. # name: George the Monkey
  217. #
  218. # reginald:
  219. # id: 2
  220. # name: Reginald the Pirate
  221. #
  222. # Each of these fixtures has two unique identifiers: one for the database
  223. # and one for the humans. Why don't we generate the primary key instead?
  224. # Hashing each fixture's label yields a consistent ID:
  225. #
  226. # george: # generated id: 503576764
  227. # name: George the Monkey
  228. #
  229. # reginald: # generated id: 324201669
  230. # name: Reginald the Pirate
  231. #
  232. # Active Record looks at the fixture's model class, discovers the correct
  233. # primary key, and generates it right before inserting the fixture
  234. # into the database.
  235. #
  236. # The generated ID for a given label is constant, so we can discover
  237. # any fixture's ID without loading anything, as long as we know the label.
  238. #
  239. # == Label references for associations (belongs_to, has_one, has_many)
  240. #
  241. # Specifying foreign keys in fixtures can be very fragile, not to
  242. # mention difficult to read. Since Active Record can figure out the ID of
  243. # any fixture from its label, you can specify FK's by label instead of ID.
  244. #
  245. # === belongs_to
  246. #
  247. # Let's break out some more monkeys and pirates.
  248. #
  249. # ### in pirates.yml
  250. #
  251. # reginald:
  252. # id: 1
  253. # name: Reginald the Pirate
  254. # monkey_id: 1
  255. #
  256. # ### in monkeys.yml
  257. #
  258. # george:
  259. # id: 1
  260. # name: George the Monkey
  261. # pirate_id: 1
  262. #
  263. # Add a few more monkeys and pirates and break this into multiple files,
  264. # and it gets pretty hard to keep track of what's going on. Let's
  265. # use labels instead of IDs:
  266. #
  267. # ### in pirates.yml
  268. #
  269. # reginald:
  270. # name: Reginald the Pirate
  271. # monkey: george
  272. #
  273. # ### in monkeys.yml
  274. #
  275. # george:
  276. # name: George the Monkey
  277. # pirate: reginald
  278. #
  279. # Pow! All is made clear. Active Record reflects on the fixture's model class,
  280. # finds all the +belongs_to+ associations, and allows you to specify
  281. # a target *label* for the *association* (monkey: george) rather than
  282. # a target *id* for the *FK* (<tt>monkey_id: 1</tt>).
  283. #
  284. # ==== Polymorphic belongs_to
  285. #
  286. # Supporting polymorphic relationships is a little bit more complicated, since
  287. # Active Record needs to know what type your association is pointing at. Something
  288. # like this should look familiar:
  289. #
  290. # ### in fruit.rb
  291. #
  292. # belongs_to :eater, polymorphic: true
  293. #
  294. # ### in fruits.yml
  295. #
  296. # apple:
  297. # id: 1
  298. # name: apple
  299. # eater_id: 1
  300. # eater_type: Monkey
  301. #
  302. # Can we do better? You bet!
  303. #
  304. # apple:
  305. # eater: george (Monkey)
  306. #
  307. # Just provide the polymorphic target type and Active Record will take care of the rest.
  308. #
  309. # === has_and_belongs_to_many
  310. #
  311. # Time to give our monkey some fruit.
  312. #
  313. # ### in monkeys.yml
  314. #
  315. # george:
  316. # id: 1
  317. # name: George the Monkey
  318. #
  319. # ### in fruits.yml
  320. #
  321. # apple:
  322. # id: 1
  323. # name: apple
  324. #
  325. # orange:
  326. # id: 2
  327. # name: orange
  328. #
  329. # grape:
  330. # id: 3
  331. # name: grape
  332. #
  333. # ### in fruits_monkeys.yml
  334. #
  335. # apple_george:
  336. # fruit_id: 1
  337. # monkey_id: 1
  338. #
  339. # orange_george:
  340. # fruit_id: 2
  341. # monkey_id: 1
  342. #
  343. # grape_george:
  344. # fruit_id: 3
  345. # monkey_id: 1
  346. #
  347. # Let's make the HABTM fixture go away.
  348. #
  349. # ### in monkeys.yml
  350. #
  351. # george:
  352. # id: 1
  353. # name: George the Monkey
  354. # fruits: apple, orange, grape
  355. #
  356. # ### in fruits.yml
  357. #
  358. # apple:
  359. # name: apple
  360. #
  361. # orange:
  362. # name: orange
  363. #
  364. # grape:
  365. # name: grape
  366. #
  367. # Zap! No more fruits_monkeys.yml file. We've specified the list of fruits
  368. # on George's fixture, but we could've just as easily specified a list
  369. # of monkeys on each fruit. As with +belongs_to+, Active Record reflects on
  370. # the fixture's model class and discovers the +has_and_belongs_to_many+
  371. # associations.
  372. #
  373. # == Autofilled Timestamp Columns
  374. #
  375. # If your table/model specifies any of Active Record's
  376. # standard timestamp columns (+created_at+, +created_on+, +updated_at+, +updated_on+),
  377. # they will automatically be set to <tt>Time.now</tt>.
  378. #
  379. # If you've set specific values, they'll be left alone.
  380. #
  381. # == Fixture label interpolation
  382. #
  383. # The label of the current fixture is always available as a column value:
  384. #
  385. # geeksomnia:
  386. # name: Geeksomnia's Account
  387. # subdomain: $LABEL
  388. # email: $LABEL@email.com
  389. #
  390. # Also, sometimes (like when porting older join table fixtures) you'll need
  391. # to be able to get a hold of the identifier for a given label. ERB
  392. # to the rescue:
  393. #
  394. # george_reginald:
  395. # monkey_id: <%= ActiveRecord::FixtureSet.identify(:reginald) %>
  396. # pirate_id: <%= ActiveRecord::FixtureSet.identify(:george) %>
  397. #
  398. # == Support for YAML defaults
  399. #
  400. # You can set and reuse defaults in your fixtures YAML file.
  401. # This is the same technique used in the +database.yml+ file to specify
  402. # defaults:
  403. #
  404. # DEFAULTS: &DEFAULTS
  405. # created_on: <%= 3.weeks.ago.to_s(:db) %>
  406. #
  407. # first:
  408. # name: Smurf
  409. # <<: *DEFAULTS
  410. #
  411. # second:
  412. # name: Fraggle
  413. # <<: *DEFAULTS
  414. #
  415. # Any fixture labeled "DEFAULTS" is safely ignored.
  416. #
  417. # == Configure the fixture model class
  418. #
  419. # It's possible to set the fixture's model class directly in the YAML file.
  420. # This is helpful when fixtures are loaded outside tests and
  421. # +set_fixture_class+ is not available (e.g.
  422. # when running <tt>rails db:fixtures:load</tt>).
  423. #
  424. # _fixture:
  425. # model_class: User
  426. # david:
  427. # name: David
  428. #
  429. # Any fixtures labeled "_fixture" are safely ignored.
  430. 1 class FixtureSet
  431. #--
  432. # An instance of FixtureSet is normally stored in a single YAML file and
  433. # possibly in a folder with the same name.
  434. #++
  435. 1 MAX_ID = 2**30 - 1
  436. 2 @@all_cached_fixtures = Hash.new { |h, k| h[k] = {} }
  437. 1 def self.default_fixture_model_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc:
  438. config.pluralize_table_names ?
  439. fixture_set_name.singularize.camelize :
  440. fixture_set_name.camelize
  441. end
  442. 1 def self.default_fixture_table_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc:
  443. "#{ config.table_name_prefix }"\
  444. "#{ fixture_set_name.tr('/', '_') }"\
  445. "#{ config.table_name_suffix }".to_sym
  446. end
  447. 1 def self.reset_cache
  448. @@all_cached_fixtures.clear
  449. end
  450. 1 def self.cache_for_connection(connection)
  451. 1 @@all_cached_fixtures[connection]
  452. end
  453. 1 def self.fixture_is_cached?(connection, table_name)
  454. cache_for_connection(connection)[table_name]
  455. end
  456. 1 def self.cached_fixtures(connection, keys_to_fetch = nil)
  457. 1 if keys_to_fetch
  458. 1 cache_for_connection(connection).values_at(*keys_to_fetch)
  459. else
  460. cache_for_connection(connection).values
  461. end
  462. end
  463. 1 def self.cache_fixtures(connection, fixtures_map)
  464. cache_for_connection(connection).update(fixtures_map)
  465. end
  466. 1 def self.instantiate_fixtures(object, fixture_set, load_instances = true)
  467. if load_instances
  468. fixture_set.each do |fixture_name, fixture|
  469. begin
  470. object.instance_variable_set "@#{fixture_name}", fixture.find
  471. rescue FixtureClassNotFound
  472. nil
  473. end
  474. end
  475. end
  476. end
  477. 1 def self.instantiate_all_loaded_fixtures(object, load_instances = true)
  478. all_loaded_fixtures.each_value do |fixture_set|
  479. instantiate_fixtures(object, fixture_set, load_instances)
  480. end
  481. end
  482. 1 cattr_accessor :all_loaded_fixtures, default: {}
  483. 1 class ClassCache
  484. 1 def initialize(class_names, config)
  485. 1 @class_names = class_names.stringify_keys
  486. 1 @config = config
  487. # Remove string values that aren't constants or subclasses of AR
  488. 1 @class_names.delete_if { |klass_name, klass| !insert_class(@class_names, klass_name, klass) }
  489. end
  490. 1 def [](fs_name)
  491. @class_names.fetch(fs_name) {
  492. klass = default_fixture_model(fs_name, @config).safe_constantize
  493. insert_class(@class_names, fs_name, klass)
  494. }
  495. end
  496. 1 private
  497. 1 def insert_class(class_names, name, klass)
  498. # We only want to deal with AR objects.
  499. if klass && klass < ActiveRecord::Base
  500. class_names[name] = klass
  501. else
  502. class_names[name] = nil
  503. end
  504. end
  505. 1 def default_fixture_model(fs_name, config)
  506. ActiveRecord::FixtureSet.default_fixture_model_name(fs_name, config)
  507. end
  508. end
  509. 1 def self.create_fixtures(fixtures_directory, fixture_set_names, class_names = {}, config = ActiveRecord::Base)
  510. 1 fixture_set_names = Array(fixture_set_names).map(&:to_s)
  511. 1 class_names = ClassCache.new class_names, config
  512. # FIXME: Apparently JK uses this.
  513. 1 connection = block_given? ? yield : ActiveRecord::Base.connection
  514. 1 files_to_read = fixture_set_names.reject { |fs_name|
  515. fixture_is_cached?(connection, fs_name)
  516. }
  517. 1 unless files_to_read.empty?
  518. fixtures_map = {}
  519. fixture_sets = files_to_read.map do |fs_name|
  520. klass = class_names[fs_name]
  521. conn = klass ? klass.connection : connection
  522. fixtures_map[fs_name] = new( # ActiveRecord::FixtureSet.new
  523. conn,
  524. fs_name,
  525. klass,
  526. ::File.join(fixtures_directory, fs_name))
  527. end
  528. update_all_loaded_fixtures fixtures_map
  529. fixture_sets_by_connection = fixture_sets.group_by { |fs| fs.model_class ? fs.model_class.connection : connection }
  530. fixture_sets_by_connection.each do |conn, set|
  531. table_rows_for_connection = Hash.new { |h, k| h[k] = [] }
  532. set.each do |fs|
  533. fs.table_rows.each do |table, rows|
  534. table_rows_for_connection[table].unshift(*rows)
  535. end
  536. end
  537. conn.insert_fixtures_set(table_rows_for_connection, table_rows_for_connection.keys)
  538. # Cap primary key sequences to max(pk).
  539. if conn.respond_to?(:reset_pk_sequence!)
  540. set.each { |fs| conn.reset_pk_sequence!(fs.table_name) }
  541. end
  542. end
  543. cache_fixtures(connection, fixtures_map)
  544. end
  545. 1 cached_fixtures(connection, fixture_set_names)
  546. end
  547. # Returns a consistent, platform-independent identifier for +label+.
  548. # Integer identifiers are values less than 2^30. UUIDs are RFC 4122 version 5 SHA-1 hashes.
  549. 1 def self.identify(label, column_type = :integer)
  550. if column_type == :uuid
  551. Digest::UUID.uuid_v5(Digest::UUID::OID_NAMESPACE, label.to_s)
  552. else
  553. Zlib.crc32(label.to_s) % MAX_ID
  554. end
  555. end
  556. # Superclass for the evaluation contexts used by ERB fixtures.
  557. 1 def self.context_class
  558. @context_class ||= Class.new
  559. end
  560. 1 def self.update_all_loaded_fixtures(fixtures_map) # :nodoc:
  561. all_loaded_fixtures.update(fixtures_map)
  562. end
  563. 1 attr_reader :table_name, :name, :fixtures, :model_class, :config
  564. 1 def initialize(connection, name, class_name, path, config = ActiveRecord::Base)
  565. @name = name
  566. @path = path
  567. @config = config
  568. self.model_class = class_name
  569. @fixtures = read_fixture_files(path)
  570. @connection = connection
  571. @table_name = (model_class.respond_to?(:table_name) ?
  572. model_class.table_name :
  573. self.class.default_fixture_table_name(name, config))
  574. end
  575. 1 def [](x)
  576. fixtures[x]
  577. end
  578. 1 def []=(k, v)
  579. fixtures[k] = v
  580. end
  581. 1 def each(&block)
  582. fixtures.each(&block)
  583. end
  584. 1 def size
  585. fixtures.size
  586. end
  587. # Returns a hash of rows to be inserted. The key is the table, the value is
  588. # a list of rows to insert to that table.
  589. 1 def table_rows
  590. now = config.default_timezone == :utc ? Time.now.utc : Time.now
  591. # allow a standard key to be used for doing defaults in YAML
  592. fixtures.delete("DEFAULTS")
  593. # track any join tables we need to insert later
  594. rows = Hash.new { |h, table| h[table] = [] }
  595. rows[table_name] = fixtures.map do |label, fixture|
  596. row = fixture.to_hash
  597. if model_class
  598. # fill in timestamp columns if they aren't specified and the model is set to record_timestamps
  599. if model_class.record_timestamps
  600. timestamp_column_names.each do |c_name|
  601. row[c_name] = now unless row.key?(c_name)
  602. end
  603. end
  604. # interpolate the fixture label
  605. row.each do |key, value|
  606. row[key] = value.gsub("$LABEL", label.to_s) if value.is_a?(String)
  607. end
  608. # generate a primary key if necessary
  609. if has_primary_key_column? && !row.include?(primary_key_name)
  610. row[primary_key_name] = ActiveRecord::FixtureSet.identify(label, primary_key_type)
  611. end
  612. # Resolve enums
  613. model_class.defined_enums.each do |name, values|
  614. if row.include?(name)
  615. row[name] = values.fetch(row[name], row[name])
  616. end
  617. end
  618. # If STI is used, find the correct subclass for association reflection
  619. reflection_class =
  620. if row.include?(inheritance_column_name)
  621. row[inheritance_column_name].constantize rescue model_class
  622. else
  623. model_class
  624. end
  625. reflection_class._reflections.each_value do |association|
  626. case association.macro
  627. when :belongs_to
  628. # Do not replace association name with association foreign key if they are named the same
  629. fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s
  630. if association.name.to_s != fk_name && value = row.delete(association.name.to_s)
  631. if association.polymorphic? && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
  632. # support polymorphic belongs_to as "label (Type)"
  633. row[association.foreign_type] = $1
  634. end
  635. fk_type = reflection_class.type_for_attribute(fk_name).type
  636. row[fk_name] = ActiveRecord::FixtureSet.identify(value, fk_type)
  637. end
  638. when :has_many
  639. if association.options[:through]
  640. add_join_records(rows, row, HasManyThroughProxy.new(association))
  641. end
  642. end
  643. end
  644. end
  645. row
  646. end
  647. rows
  648. end
  649. 1 class ReflectionProxy # :nodoc:
  650. 1 def initialize(association)
  651. @association = association
  652. end
  653. 1 def join_table
  654. @association.join_table
  655. end
  656. 1 def name
  657. @association.name
  658. end
  659. 1 def primary_key_type
  660. @association.klass.type_for_attribute(@association.klass.primary_key).type
  661. end
  662. end
  663. 1 class HasManyThroughProxy < ReflectionProxy # :nodoc:
  664. 1 def rhs_key
  665. @association.foreign_key
  666. end
  667. 1 def lhs_key
  668. @association.through_reflection.foreign_key
  669. end
  670. 1 def join_table
  671. @association.through_reflection.table_name
  672. end
  673. end
  674. 1 private
  675. 1 def primary_key_name
  676. @primary_key_name ||= model_class && model_class.primary_key
  677. end
  678. 1 def primary_key_type
  679. @primary_key_type ||= model_class && model_class.type_for_attribute(model_class.primary_key).type
  680. end
  681. 1 def add_join_records(rows, row, association)
  682. # This is the case when the join table has no fixtures file
  683. if (targets = row.delete(association.name.to_s))
  684. table_name = association.join_table
  685. column_type = association.primary_key_type
  686. lhs_key = association.lhs_key
  687. rhs_key = association.rhs_key
  688. targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
  689. rows[table_name].concat targets.map { |target|
  690. { lhs_key => row[primary_key_name],
  691. rhs_key => ActiveRecord::FixtureSet.identify(target, column_type) }
  692. }
  693. end
  694. end
  695. 1 def has_primary_key_column?
  696. @has_primary_key_column ||= primary_key_name &&
  697. model_class.columns.any? { |c| c.name == primary_key_name }
  698. end
  699. 1 def timestamp_column_names
  700. @timestamp_column_names ||=
  701. %w(created_at created_on updated_at updated_on) & column_names
  702. end
  703. 1 def inheritance_column_name
  704. @inheritance_column_name ||= model_class && model_class.inheritance_column
  705. end
  706. 1 def column_names
  707. @column_names ||= @connection.columns(@table_name).collect(&:name)
  708. end
  709. 1 def model_class=(class_name)
  710. if class_name.is_a?(Class) # TODO: Should be an AR::Base type class, or any?
  711. @model_class = class_name
  712. else
  713. @model_class = class_name.safe_constantize if class_name
  714. end
  715. end
  716. # Loads the fixtures from the YAML file at +path+.
  717. # If the file sets the +model_class+ and current instance value is not set,
  718. # it uses the file value.
  719. 1 def read_fixture_files(path)
  720. yaml_files = Dir["#{path}/{**,*}/*.yml"].select { |f|
  721. ::File.file?(f)
  722. } + [yaml_file_path(path)]
  723. yaml_files.each_with_object({}) do |file, fixtures|
  724. FixtureSet::File.open(file) do |fh|
  725. self.model_class ||= fh.model_class if fh.model_class
  726. fh.each do |fixture_name, row|
  727. fixtures[fixture_name] = ActiveRecord::Fixture.new(row, model_class)
  728. end
  729. end
  730. end
  731. end
  732. 1 def yaml_file_path(path)
  733. "#{path}.yml"
  734. end
  735. end
  736. 1 class Fixture #:nodoc:
  737. 1 include Enumerable
  738. 1 class FixtureError < StandardError #:nodoc:
  739. end
  740. 1 class FormatError < FixtureError #:nodoc:
  741. end
  742. 1 attr_reader :model_class, :fixture
  743. 1 def initialize(fixture, model_class)
  744. @fixture = fixture
  745. @model_class = model_class
  746. end
  747. 1 def class_name
  748. model_class.name if model_class
  749. end
  750. 1 def each
  751. fixture.each { |item| yield item }
  752. end
  753. 1 def [](key)
  754. fixture[key]
  755. end
  756. 1 alias :to_hash :fixture
  757. 1 def find
  758. if model_class
  759. model_class.unscoped do
  760. model_class.find(fixture[model_class.primary_key])
  761. end
  762. else
  763. raise FixtureClassNotFound, "No class attached to find."
  764. end
  765. end
  766. end
  767. end
  768. 1 module ActiveRecord
  769. 1 module TestFixtures
  770. 1 extend ActiveSupport::Concern
  771. 1 def before_setup # :nodoc:
  772. 2 setup_fixtures
  773. 2 super
  774. end
  775. 1 def after_teardown # :nodoc:
  776. 2 super
  777. 2 teardown_fixtures
  778. end
  779. 1 included do
  780. 1 class_attribute :fixture_path, instance_writer: false
  781. 1 class_attribute :fixture_table_names, default: []
  782. 1 class_attribute :fixture_class_names, default: {}
  783. 1 class_attribute :use_transactional_tests, default: true
  784. 1 class_attribute :use_instantiated_fixtures, default: false # true, false, or :no_instances
  785. 1 class_attribute :pre_loaded_fixtures, default: false
  786. 1 class_attribute :config, default: ActiveRecord::Base
  787. end
  788. 1 module ClassMethods
  789. # Sets the model class for a fixture when the class name cannot be inferred from the fixture name.
  790. #
  791. # Examples:
  792. #
  793. # set_fixture_class some_fixture: SomeModel,
  794. # 'namespaced/fixture' => Another::Model
  795. #
  796. # The keys must be the fixture names, that coincide with the short paths to the fixture files.
  797. 1 def set_fixture_class(class_names = {})
  798. self.fixture_class_names = fixture_class_names.merge(class_names.stringify_keys)
  799. end
  800. 1 def fixtures(*fixture_set_names)
  801. if fixture_set_names.first == :all
  802. fixture_set_names = Dir["#{fixture_path}/{**,*}/*.{yml}"].uniq
  803. fixture_set_names.map! { |f| f[(fixture_path.to_s.size + 1)..-5] }
  804. else
  805. fixture_set_names = fixture_set_names.flatten.map(&:to_s)
  806. end
  807. self.fixture_table_names |= fixture_set_names
  808. setup_fixture_accessors(fixture_set_names)
  809. end
  810. 1 def setup_fixture_accessors(fixture_set_names = nil)
  811. fixture_set_names = Array(fixture_set_names || fixture_table_names)
  812. methods = Module.new do
  813. fixture_set_names.each do |fs_name|
  814. fs_name = fs_name.to_s
  815. accessor_name = fs_name.tr("/", "_").to_sym
  816. define_method(accessor_name) do |*fixture_names|
  817. force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
  818. return_single_record = fixture_names.size == 1
  819. fixture_names = @loaded_fixtures[fs_name].fixtures.keys if fixture_names.empty?
  820. @fixture_cache[fs_name] ||= {}
  821. instances = fixture_names.map do |f_name|
  822. f_name = f_name.to_s if f_name.is_a?(Symbol)
  823. @fixture_cache[fs_name].delete(f_name) if force_reload
  824. if @loaded_fixtures[fs_name][f_name]
  825. @fixture_cache[fs_name][f_name] ||= @loaded_fixtures[fs_name][f_name].find
  826. else
  827. raise StandardError, "No fixture named '#{f_name}' found for fixture set '#{fs_name}'"
  828. end
  829. end
  830. return_single_record ? instances.first : instances
  831. end
  832. private accessor_name
  833. end
  834. end
  835. include methods
  836. end
  837. 1 def uses_transaction(*methods)
  838. @uses_transaction = [] unless defined?(@uses_transaction)
  839. @uses_transaction.concat methods.map(&:to_s)
  840. end
  841. 1 def uses_transaction?(method)
  842. 4 @uses_transaction = [] unless defined?(@uses_transaction)
  843. 4 @uses_transaction.include?(method.to_s)
  844. end
  845. end
  846. 1 def run_in_transaction?
  847. 4 use_transactional_tests &&
  848. 3 !self.class.uses_transaction?(method_name)
  849. end
  850. 1 def setup_fixtures(config = ActiveRecord::Base)
  851. 2 if pre_loaded_fixtures && !use_transactional_tests
  852. raise RuntimeError, "pre_loaded_fixtures requires use_transactional_tests"
  853. end
  854. 2 @fixture_cache = {}
  855. 2 @fixture_connections = []
  856. 2 @@already_loaded_fixtures ||= {}
  857. 2 @connection_subscriber = nil
  858. # Load fixtures once and begin transaction.
  859. 2 if run_in_transaction?
  860. 2 if @@already_loaded_fixtures[self.class]
  861. 1 @loaded_fixtures = @@already_loaded_fixtures[self.class]
  862. else
  863. 1 @loaded_fixtures = load_fixtures(config)
  864. 1 @@already_loaded_fixtures[self.class] = @loaded_fixtures
  865. end
  866. # Begin transactions for connections already established
  867. 2 @fixture_connections = enlist_fixture_connections
  868. 2 @fixture_connections.each do |connection|
  869. 2 connection.begin_transaction joinable: false
  870. 2 connection.pool.lock_thread = true
  871. end
  872. # When connections are established in the future, begin a transaction too
  873. 2 @connection_subscriber = ActiveSupport::Notifications.subscribe("!connection.active_record") do |_, _, _, _, payload|
  874. spec_name = payload[:spec_name] if payload.key?(:spec_name)
  875. if spec_name
  876. begin
  877. connection = ActiveRecord::Base.connection_handler.retrieve_connection(spec_name)
  878. rescue ConnectionNotEstablished
  879. connection = nil
  880. end
  881. if connection && !@fixture_connections.include?(connection)
  882. connection.begin_transaction joinable: false
  883. connection.pool.lock_thread = true
  884. @fixture_connections << connection
  885. end
  886. end
  887. end
  888. # Load fixtures for every test.
  889. else
  890. ActiveRecord::FixtureSet.reset_cache
  891. @@already_loaded_fixtures[self.class] = nil
  892. @loaded_fixtures = load_fixtures(config)
  893. end
  894. # Instantiate fixtures for every test if requested.
  895. 2 instantiate_fixtures if use_instantiated_fixtures
  896. end
  897. 1 def teardown_fixtures
  898. # Rollback changes if a transaction is active.
  899. 2 if run_in_transaction?
  900. 2 ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber
  901. 2 @fixture_connections.each do |connection|
  902. 2 connection.rollback_transaction if connection.transaction_open?
  903. 2 connection.pool.lock_thread = false
  904. end
  905. 2 @fixture_connections.clear
  906. else
  907. ActiveRecord::FixtureSet.reset_cache
  908. end
  909. 2 ActiveRecord::Base.clear_active_connections!
  910. end
  911. 1 def enlist_fixture_connections
  912. 2 ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection)
  913. end
  914. 1 private
  915. 1 def load_fixtures(config)
  916. 1 fixtures = ActiveRecord::FixtureSet.create_fixtures(fixture_path, fixture_table_names, fixture_class_names, config)
  917. 1 Hash[fixtures.map { |f| [f.name, f] }]
  918. end
  919. 1 def instantiate_fixtures
  920. if pre_loaded_fixtures
  921. raise RuntimeError, "Load fixtures before instantiating them." if ActiveRecord::FixtureSet.all_loaded_fixtures.empty?
  922. ActiveRecord::FixtureSet.instantiate_all_loaded_fixtures(self, load_instances?)
  923. else
  924. raise RuntimeError, "Load fixtures before instantiating them." if @loaded_fixtures.nil?
  925. @loaded_fixtures.each_value do |fixture_set|
  926. ActiveRecord::FixtureSet.instantiate_fixtures(self, fixture_set, load_instances?)
  927. end
  928. end
  929. end
  930. 1 def load_instances?
  931. use_instantiated_fixtures != :no_instances
  932. end
  933. end
  934. end
  935. 1 class ActiveRecord::FixtureSet::RenderContext # :nodoc:
  936. 1 def self.create_subclass
  937. Class.new ActiveRecord::FixtureSet.context_class do
  938. def get_binding
  939. binding()
  940. end
  941. def binary(path)
  942. %(!!binary "#{Base64.strict_encode64(File.read(path))}")
  943. end
  944. end
  945. end
  946. end

target/rubygems/gems/activerecord-5.2.3/lib/active_record/legacy_yaml_adapter.rb

34.78% lines covered

23 relevant lines. 8 lines covered and 15 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveRecord
  3. 1 module LegacyYamlAdapter
  4. 1 def self.convert(klass, coder)
  5. 2 return coder unless coder.is_a?(Psych::Coder)
  6. case coder["active_record_yaml_version"]
  7. when 1, 2 then coder
  8. else
  9. if coder["attributes"].is_a?(ActiveModel::AttributeSet)
  10. Rails420.convert(klass, coder)
  11. else
  12. Rails41.convert(klass, coder)
  13. end
  14. end
  15. end
  16. 1 module Rails420
  17. 1 def self.convert(klass, coder)
  18. attribute_set = coder["attributes"]
  19. klass.attribute_names.each do |attr_name|
  20. attribute = attribute_set[attr_name]
  21. if attribute.type.is_a?(Delegator)
  22. type_from_klass = klass.type_for_attribute(attr_name)
  23. attribute_set[attr_name] = attribute.with_type(type_from_klass)
  24. end
  25. end
  26. coder
  27. end
  28. end
  29. 1 module Rails41
  30. 1 def self.convert(klass, coder)
  31. attributes = klass.attributes_builder
  32. .build_from_database(coder["attributes"])
  33. new_record = coder["attributes"][klass.primary_key].blank?
  34. {
  35. "attributes" => attributes,
  36. "new_record" => new_record,
  37. }
  38. end
  39. end
  40. end
  41. end

target/rubygems/gems/activerecord-5.2.3/lib/active_record/railties/controller_runtime.rb

96.67% lines covered

30 relevant lines. 29 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/module/attr_internal"
  3. 1 require "active_record/log_subscriber"
  4. 1 module ActiveRecord
  5. 1 module Railties # :nodoc:
  6. 1 module ControllerRuntime #:nodoc:
  7. 1 extend ActiveSupport::Concern
  8. # TODO Change this to private once we've dropped Ruby 2.2 support.
  9. # Workaround for Ruby 2.2 "private attribute?" warning.
  10. 1 protected
  11. 1 attr_internal :db_runtime
  12. 1 private
  13. 1 def process_action(action, *args)
  14. # We also need to reset the runtime before each action
  15. # because of queries in middleware or in cases we are streaming
  16. # and it won't be cleaned up by the method below.
  17. 2 ActiveRecord::LogSubscriber.reset_runtime
  18. 2 super
  19. end
  20. 1 def cleanup_view_runtime
  21. 1 if logger && logger.info? && ActiveRecord::Base.connected?
  22. 1 db_rt_before_render = ActiveRecord::LogSubscriber.reset_runtime
  23. 1 self.db_runtime = (db_runtime || 0) + db_rt_before_render
  24. 1 runtime = super
  25. 1 db_rt_after_render = ActiveRecord::LogSubscriber.reset_runtime
  26. 1 self.db_runtime += db_rt_after_render
  27. 1 runtime - db_rt_after_render
  28. else
  29. super
  30. end
  31. end
  32. 1 def append_info_to_payload(payload)
  33. 2 super
  34. 2 if ActiveRecord::Base.connected?
  35. 2 payload[:db_runtime] = (db_runtime || 0) + ActiveRecord::LogSubscriber.reset_runtime
  36. end
  37. end
  38. 1 module ClassMethods # :nodoc:
  39. 1 def log_process_action(payload)
  40. 2 messages, db_runtime = super, payload[:db_runtime]
  41. 2 messages << ("ActiveRecord: %.1fms" % db_runtime.to_f) if db_runtime
  42. 2 messages
  43. end
  44. end
  45. end
  46. end
  47. end

target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation.rb

39.65% lines covered

227 relevant lines. 90 lines covered and 137 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveRecord
  3. # = Active Record \Relation
  4. 1 class Relation
  5. 1 MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group,
  6. :order, :joins, :left_outer_joins, :references,
  7. :extending, :unscope]
  8. 1 SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering,
  9. :reverse_order, :distinct, :create_with, :skip_query_cache]
  10. 1 CLAUSE_METHODS = [:where, :having, :from]
  11. 1 INVALID_METHODS_FOR_DELETE_ALL = [:distinct, :group, :having]
  12. 1 VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + CLAUSE_METHODS
  13. 1 include Enumerable
  14. 1 include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
  15. 1 attr_reader :table, :klass, :loaded, :predicate_builder
  16. 1 alias :model :klass
  17. 1 alias :loaded? :loaded
  18. 1 alias :locked? :lock_value
  19. 4 def initialize(klass, table: klass.arel_table, predicate_builder: klass.predicate_builder, values: {})
  20. 3 @klass = klass
  21. 3 @table = table
  22. 3 @values = values
  23. 3 @offsets = {}
  24. 3 @loaded = false
  25. 3 @predicate_builder = predicate_builder
  26. 3 @delegate_to_klass = false
  27. end
  28. 1 def initialize_copy(other)
  29. 6 @values = @values.dup
  30. 6 reset
  31. end
  32. 1 def arel_attribute(name) # :nodoc:
  33. 4 klass.arel_attribute(name, table)
  34. end
  35. # Initializes new record from relation while maintaining the current
  36. # scope.
  37. #
  38. # Expects arguments in the same format as {ActiveRecord::Base.new}[rdoc-ref:Core.new].
  39. #
  40. # users = User.where(name: 'DHH')
  41. # user = users.new # => #<User id: nil, name: "DHH", created_at: nil, updated_at: nil>
  42. #
  43. # You can also pass a block to new with the new record as argument:
  44. #
  45. # user = users.new { |user| user.name = 'Oscar' }
  46. # user.name # => Oscar
  47. 1 def new(attributes = nil, &block)
  48. scoping { klass.new(values_for_create(attributes), &block) }
  49. end
  50. 1 alias build new
  51. # Tries to create a new record with the same scoped attributes
  52. # defined in the relation. Returns the initialized object if validation fails.
  53. #
  54. # Expects arguments in the same format as
  55. # {ActiveRecord::Base.create}[rdoc-ref:Persistence::ClassMethods#create].
  56. #
  57. # ==== Examples
  58. #
  59. # users = User.where(name: 'Oscar')
  60. # users.create # => #<User id: 3, name: "Oscar", ...>
  61. #
  62. # users.create(name: 'fxn')
  63. # users.create # => #<User id: 4, name: "fxn", ...>
  64. #
  65. # users.create { |user| user.name = 'tenderlove' }
  66. # # => #<User id: 5, name: "tenderlove", ...>
  67. #
  68. # users.create(name: nil) # validation on name
  69. # # => #<User id: nil, name: nil, ...>
  70. 1 def create(attributes = nil, &block)
  71. if attributes.is_a?(Array)
  72. attributes.collect { |attr| create(attr, &block) }
  73. else
  74. scoping { klass.create(values_for_create(attributes), &block) }
  75. end
  76. end
  77. # Similar to #create, but calls
  78. # {create!}[rdoc-ref:Persistence::ClassMethods#create!]
  79. # on the base class. Raises an exception if a validation error occurs.
  80. #
  81. # Expects arguments in the same format as
  82. # {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!].
  83. 1 def create!(attributes = nil, &block)
  84. if attributes.is_a?(Array)
  85. attributes.collect { |attr| create!(attr, &block) }
  86. else
  87. scoping { klass.create!(values_for_create(attributes), &block) }
  88. end
  89. end
  90. 1 def first_or_create(attributes = nil, &block) # :nodoc:
  91. first || create(attributes, &block)
  92. end
  93. 1 def first_or_create!(attributes = nil, &block) # :nodoc:
  94. first || create!(attributes, &block)
  95. end
  96. 1 def first_or_initialize(attributes = nil, &block) # :nodoc:
  97. first || new(attributes, &block)
  98. end
  99. # Finds the first record with the given attributes, or creates a record
  100. # with the attributes if one is not found:
  101. #
  102. # # Find the first user named "Pen��lope" or create a new one.
  103. # User.find_or_create_by(first_name: 'Pen��lope')
  104. # # => #<User id: 1, first_name: "Pen��lope", last_name: nil>
  105. #
  106. # # Find the first user named "Pen��lope" or create a new one.
  107. # # We already have one so the existing record will be returned.
  108. # User.find_or_create_by(first_name: 'Pen��lope')
  109. # # => #<User id: 1, first_name: "Pen��lope", last_name: nil>
  110. #
  111. # # Find the first user named "Scarlett" or create a new one with
  112. # # a particular last name.
  113. # User.create_with(last_name: 'Johansson').find_or_create_by(first_name: 'Scarlett')
  114. # # => #<User id: 2, first_name: "Scarlett", last_name: "Johansson">
  115. #
  116. # This method accepts a block, which is passed down to #create. The last example
  117. # above can be alternatively written this way:
  118. #
  119. # # Find the first user named "Scarlett" or create a new one with a
  120. # # different last name.
  121. # User.find_or_create_by(first_name: 'Scarlett') do |user|
  122. # user.last_name = 'Johansson'
  123. # end
  124. # # => #<User id: 2, first_name: "Scarlett", last_name: "Johansson">
  125. #
  126. # This method always returns a record, but if creation was attempted and
  127. # failed due to validation errors it won't be persisted, you get what
  128. # #create returns in such situation.
  129. #
  130. # Please note *this method is not atomic*, it runs first a SELECT, and if
  131. # there are no results an INSERT is attempted. If there are other threads
  132. # or processes there is a race condition between both calls and it could
  133. # be the case that you end up with two similar records.
  134. #
  135. # Whether that is a problem or not depends on the logic of the
  136. # application, but in the particular case in which rows have a UNIQUE
  137. # constraint an exception may be raised, just retry:
  138. #
  139. # begin
  140. # CreditAccount.transaction(requires_new: true) do
  141. # CreditAccount.find_or_create_by(user_id: user.id)
  142. # end
  143. # rescue ActiveRecord::RecordNotUnique
  144. # retry
  145. # end
  146. #
  147. 1 def find_or_create_by(attributes, &block)
  148. find_by(attributes) || create(attributes, &block)
  149. end
  150. # Like #find_or_create_by, but calls
  151. # {create!}[rdoc-ref:Persistence::ClassMethods#create!] so an exception
  152. # is raised if the created record is invalid.
  153. 1 def find_or_create_by!(attributes, &block)
  154. find_by(attributes) || create!(attributes, &block)
  155. end
  156. # Like #find_or_create_by, but calls {new}[rdoc-ref:Core#new]
  157. # instead of {create}[rdoc-ref:Persistence::ClassMethods#create].
  158. 1 def find_or_initialize_by(attributes, &block)
  159. find_by(attributes) || new(attributes, &block)
  160. end
  161. # Runs EXPLAIN on the query or queries triggered by this relation and
  162. # returns the result as a string. The string is formatted imitating the
  163. # ones printed by the database shell.
  164. #
  165. # Note that this method actually runs the queries, since the results of some
  166. # are needed by the next ones when eager loading is going on.
  167. #
  168. # Please see further details in the
  169. # {Active Record Query Interface guide}[http://guides.rubyonrails.org/active_record_querying.html#running-explain].
  170. 1 def explain
  171. exec_explain(collecting_queries_for_explain { exec_queries })
  172. end
  173. # Converts relation objects to Array.
  174. 1 def to_ary
  175. records.dup
  176. end
  177. 1 alias to_a to_ary
  178. 1 def records # :nodoc:
  179. load
  180. @records
  181. end
  182. # Serializes the relation objects Array.
  183. 1 def encode_with(coder)
  184. coder.represent_seq(nil, records)
  185. end
  186. # Returns size of the records.
  187. 1 def size
  188. loaded? ? @records.length : count(:all)
  189. end
  190. # Returns true if there are no records.
  191. 1 def empty?
  192. return @records.empty? if loaded?
  193. !exists?
  194. end
  195. # Returns true if there are no records.
  196. 1 def none?
  197. return super if block_given?
  198. empty?
  199. end
  200. # Returns true if there are any records.
  201. 1 def any?
  202. return super if block_given?
  203. !empty?
  204. end
  205. # Returns true if there is exactly one record.
  206. 1 def one?
  207. return super if block_given?
  208. limit_value ? records.one? : size == 1
  209. end
  210. # Returns true if there is more than one record.
  211. 1 def many?
  212. return super if block_given?
  213. limit_value ? records.many? : size > 1
  214. end
  215. # Returns a cache key that can be used to identify the records fetched by
  216. # this query. The cache key is built with a fingerprint of the sql query,
  217. # the number of records matched by the query and a timestamp of the last
  218. # updated record. When a new record comes to match the query, or any of
  219. # the existing records is updated or deleted, the cache key changes.
  220. #
  221. # Product.where("name like ?", "%Cosmic Encounter%").cache_key
  222. # # => "products/query-1850ab3d302391b85b8693e941286659-1-20150714212553907087000"
  223. #
  224. # If the collection is loaded, the method will iterate through the records
  225. # to generate the timestamp, otherwise it will trigger one SQL query like:
  226. #
  227. # SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%')
  228. #
  229. # You can also pass a custom timestamp column to fetch the timestamp of the
  230. # last updated record.
  231. #
  232. # Product.where("name like ?", "%Game%").cache_key(:last_reviewed_at)
  233. #
  234. # You can customize the strategy to generate the key on a per model basis
  235. # overriding ActiveRecord::Base#collection_cache_key.
  236. 1 def cache_key(timestamp_column = :updated_at)
  237. @cache_keys ||= {}
  238. @cache_keys[timestamp_column] ||= @klass.collection_cache_key(self, timestamp_column)
  239. end
  240. # Scope all queries to the current scope.
  241. #
  242. # Comment.where(post_id: 1).scoping do
  243. # Comment.first
  244. # end
  245. # # => SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 ORDER BY "comments"."id" ASC LIMIT 1
  246. #
  247. # Please check unscoped if you want to remove all previous scopes (including
  248. # the default_scope) during the execution of a block.
  249. 1 def scoping
  250. previous, klass.current_scope = klass.current_scope(true), self unless @delegate_to_klass
  251. yield
  252. ensure
  253. klass.current_scope = previous unless @delegate_to_klass
  254. end
  255. 1 def _exec_scope(*args, &block) # :nodoc:
  256. @delegate_to_klass = true
  257. instance_exec(*args, &block) || self
  258. ensure
  259. @delegate_to_klass = false
  260. end
  261. # Updates all records in the current relation with details given. This method constructs a single SQL UPDATE
  262. # statement and sends it straight to the database. It does not instantiate the involved models and it does not
  263. # trigger Active Record callbacks or validations. However, values passed to #update_all will still go through
  264. # Active Record's normal type casting and serialization.
  265. #
  266. # ==== Parameters
  267. #
  268. # * +updates+ - A string, array, or hash representing the SET part of an SQL statement.
  269. #
  270. # ==== Examples
  271. #
  272. # # Update all customers with the given attributes
  273. # Customer.update_all wants_email: true
  274. #
  275. # # Update all books with 'Rails' in their title
  276. # Book.where('title LIKE ?', '%Rails%').update_all(author: 'David')
  277. #
  278. # # Update all books that match conditions, but limit it to 5 ordered by date
  279. # Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(author: 'David')
  280. #
  281. # # Update all invoices and set the number column to its id value.
  282. # Invoice.update_all('number = id')
  283. 1 def update_all(updates)
  284. raise ArgumentError, "Empty list of attributes to change" if updates.blank?
  285. if eager_loading?
  286. relation = apply_join_dependency
  287. return relation.update_all(updates)
  288. end
  289. stmt = Arel::UpdateManager.new
  290. stmt.set Arel.sql(@klass.sanitize_sql_for_assignment(updates))
  291. stmt.table(table)
  292. if has_join_values? || offset_value
  293. @klass.connection.join_to_update(stmt, arel, arel_attribute(primary_key))
  294. else
  295. stmt.key = arel_attribute(primary_key)
  296. stmt.take(arel.limit)
  297. stmt.order(*arel.orders)
  298. stmt.wheres = arel.constraints
  299. end
  300. @klass.connection.update stmt, "#{@klass} Update All"
  301. end
  302. 1 def update(id = :all, attributes) # :nodoc:
  303. if id == :all
  304. each { |record| record.update(attributes) }
  305. else
  306. klass.update(id, attributes)
  307. end
  308. end
  309. # Destroys the records by instantiating each
  310. # record and calling its {#destroy}[rdoc-ref:Persistence#destroy] method.
  311. # Each object's callbacks are executed (including <tt>:dependent</tt> association options).
  312. # Returns the collection of objects that were destroyed; each will be frozen, to
  313. # reflect that no changes should be made (since they can't be persisted).
  314. #
  315. # Note: Instantiation, callback execution, and deletion of each
  316. # record can be time consuming when you're removing many records at
  317. # once. It generates at least one SQL +DELETE+ query per record (or
  318. # possibly more, to enforce your callbacks). If you want to delete many
  319. # rows quickly, without concern for their associations or callbacks, use
  320. # #delete_all instead.
  321. #
  322. # ==== Examples
  323. #
  324. # Person.where(age: 0..18).destroy_all
  325. 1 def destroy_all
  326. records.each(&:destroy).tap { reset }
  327. end
  328. # Deletes the records without instantiating the records
  329. # first, and hence not calling the {#destroy}[rdoc-ref:Persistence#destroy]
  330. # method nor invoking callbacks.
  331. # This is a single SQL DELETE statement that goes straight to the database, much more
  332. # efficient than #destroy_all. Be careful with relations though, in particular
  333. # <tt>:dependent</tt> rules defined on associations are not honored. Returns the
  334. # number of rows affected.
  335. #
  336. # Post.where(person_id: 5).where(category: ['Something', 'Else']).delete_all
  337. #
  338. # Both calls delete the affected posts all at once with a single DELETE statement.
  339. # If you need to destroy dependent associations or call your <tt>before_*</tt> or
  340. # +after_destroy+ callbacks, use the #destroy_all method instead.
  341. #
  342. # If an invalid method is supplied, #delete_all raises an ActiveRecordError:
  343. #
  344. # Post.distinct.delete_all
  345. # # => ActiveRecord::ActiveRecordError: delete_all doesn't support distinct
  346. 1 def delete_all
  347. invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|
  348. value = get_value(method)
  349. SINGLE_VALUE_METHODS.include?(method) ? value : value.any?
  350. end
  351. if invalid_methods.any?
  352. raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
  353. end
  354. if eager_loading?
  355. relation = apply_join_dependency
  356. return relation.delete_all
  357. end
  358. stmt = Arel::DeleteManager.new
  359. stmt.from(table)
  360. if has_join_values? || has_limit_or_offset?
  361. @klass.connection.join_to_delete(stmt, arel, arel_attribute(primary_key))
  362. else
  363. stmt.wheres = arel.constraints
  364. end
  365. affected = @klass.connection.delete(stmt, "#{@klass} Destroy")
  366. reset
  367. affected
  368. end
  369. # Causes the records to be loaded from the database if they have not
  370. # been loaded already. You can use this if for some reason you need
  371. # to explicitly load some records before actually using them. The
  372. # return value is the relation itself, not the records.
  373. #
  374. # Post.where(published: true).load # => #<ActiveRecord::Relation>
  375. 1 def load(&block)
  376. exec_queries(&block) unless loaded?
  377. self
  378. end
  379. # Forces reloading of relation.
  380. 1 def reload
  381. reset
  382. load
  383. end
  384. 1 def reset
  385. 6 @delegate_to_klass = false
  386. 6 @to_sql = @arel = @loaded = @should_eager_load = nil
  387. 6 @records = [].freeze
  388. 6 @offsets = {}
  389. 6 self
  390. end
  391. # Returns sql statement for the relation.
  392. #
  393. # User.where(name: 'Oscar').to_sql
  394. # # => SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar'
  395. 1 def to_sql
  396. @to_sql ||= begin
  397. if eager_loading?
  398. apply_join_dependency do |relation, join_dependency|
  399. relation = join_dependency.apply_column_aliases(relation)
  400. relation.to_sql
  401. end
  402. else
  403. conn = klass.connection
  404. conn.unprepared_statement { conn.to_sql(arel) }
  405. end
  406. end
  407. end
  408. # Returns a hash of where conditions.
  409. #
  410. # User.where(name: 'Oscar').where_values_hash
  411. # # => {name: "Oscar"}
  412. 1 def where_values_hash(relation_table_name = klass.table_name)
  413. where_clause.to_h(relation_table_name)
  414. end
  415. 1 def scope_for_create
  416. where_values_hash.merge!(create_with_value.stringify_keys)
  417. end
  418. # Returns true if relation needs eager loading.
  419. 1 def eager_loading?
  420. 2 @should_eager_load ||=
  421. 1 eager_load_values.any? ||
  422. 2 includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?)
  423. end
  424. # Joins that are also marked for preloading. In which case we should just eager load them.
  425. # Note that this is a naive implementation because we could have strings and symbols which
  426. # represent the same association, but that aren't matched by this. Also, we could have
  427. # nested hashes which partially match, e.g. { a: :b } & { a: [:b, :c] }
  428. 1 def joined_includes_values
  429. includes_values & joins_values
  430. end
  431. # Compares two relations for equality.
  432. 1 def ==(other)
  433. case other
  434. when Associations::CollectionProxy, AssociationRelation
  435. self == other.records
  436. when Relation
  437. other.to_sql == to_sql
  438. when Array
  439. records == other
  440. end
  441. end
  442. 1 def pretty_print(q)
  443. q.pp(records)
  444. end
  445. # Returns true if relation is blank.
  446. 1 def blank?
  447. records.blank?
  448. end
  449. 1 def values
  450. @values.dup
  451. end
  452. 1 def inspect
  453. subject = loaded? ? records : self
  454. entries = subject.take([limit_value, 11].compact.min).map!(&:inspect)
  455. entries[10] = "..." if entries.size == 11
  456. "#<#{self.class.name} [#{entries.join(', ')}]>"
  457. end
  458. 1 def empty_scope? # :nodoc:
  459. @values == klass.unscoped.values
  460. end
  461. 1 def has_limit_or_offset? # :nodoc:
  462. limit_value || offset_value
  463. end
  464. 1 def alias_tracker(joins = [], aliases = nil) # :nodoc:
  465. joins += [aliases] if aliases
  466. ActiveRecord::Associations::AliasTracker.create(connection, table.name, joins)
  467. end
  468. 1 protected
  469. 1 def load_records(records)
  470. @records = records.freeze
  471. @loaded = true
  472. end
  473. 1 private
  474. 1 def has_join_values?
  475. joins_values.any? || left_outer_joins_values.any?
  476. end
  477. 1 def exec_queries(&block)
  478. skip_query_cache_if_necessary do
  479. @records =
  480. if eager_loading?
  481. apply_join_dependency do |relation, join_dependency|
  482. if ActiveRecord::NullRelation === relation
  483. []
  484. else
  485. relation = join_dependency.apply_column_aliases(relation)
  486. rows = connection.select_all(relation.arel, "SQL")
  487. join_dependency.instantiate(rows, &block)
  488. end.freeze
  489. end
  490. else
  491. klass.find_by_sql(arel, &block).freeze
  492. end
  493. preload = preload_values
  494. preload += includes_values unless eager_loading?
  495. preloader = nil
  496. preload.each do |associations|
  497. preloader ||= build_preloader
  498. preloader.preload @records, associations
  499. end
  500. @records.each(&:readonly!) if readonly_value
  501. @loaded = true
  502. @records
  503. end
  504. end
  505. 1 def skip_query_cache_if_necessary
  506. 2 if skip_query_cache_value
  507. uncached do
  508. yield
  509. end
  510. else
  511. 2 yield
  512. end
  513. end
  514. 1 def build_preloader
  515. ActiveRecord::Associations::Preloader.new
  516. end
  517. 1 def references_eager_loaded_tables?
  518. joined_tables = arel.join_sources.map do |join|
  519. if join.is_a?(Arel::Nodes::StringJoin)
  520. tables_in_string(join.left)
  521. else
  522. [join.left.table_name, join.left.table_alias]
  523. end
  524. end
  525. joined_tables += [table.name, table.table_alias]
  526. # always convert table names to downcase as in Oracle quoted table names are in uppercase
  527. joined_tables = joined_tables.flatten.compact.map(&:downcase).uniq
  528. (references_values - joined_tables).any?
  529. end
  530. 1 def tables_in_string(string)
  531. return [] if string.blank?
  532. # always convert table names to downcase as in Oracle quoted table names are in uppercase
  533. # ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries
  534. string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map(&:downcase).uniq - ["raw_sql_"]
  535. end
  536. 1 def values_for_create(attributes = nil)
  537. result = attributes ? where_values_hash.merge!(attributes) : where_values_hash
  538. # NOTE: if there are same keys in both create_with and result, create_with should be used.
  539. # This is to make sure nested attributes don't get passed to the klass.new,
  540. # while keeping the precedence of the duplicate keys in create_with.
  541. create_with_value.stringify_keys.each do |k, v|
  542. result[k] = v if result.key?(k)
  543. end
  544. result
  545. end
  546. end
  547. end

target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/batches.rb

15.94% lines covered

69 relevant lines. 11 lines covered and 58 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_record/relation/batches/batch_enumerator"
  3. 1 module ActiveRecord
  4. 1 module Batches
  5. 1 ORDER_IGNORE_MESSAGE = "Scoped order is ignored, it's forced to be batch order."
  6. # Looping through a collection of records from the database
  7. # (using the Scoping::Named::ClassMethods.all method, for example)
  8. # is very inefficient since it will try to instantiate all the objects at once.
  9. #
  10. # In that case, batch processing methods allow you to work
  11. # with the records in batches, thereby greatly reducing memory consumption.
  12. #
  13. # The #find_each method uses #find_in_batches with a batch size of 1000 (or as
  14. # specified by the +:batch_size+ option).
  15. #
  16. # Person.find_each do |person|
  17. # person.do_awesome_stuff
  18. # end
  19. #
  20. # Person.where("age > 21").find_each do |person|
  21. # person.party_all_night!
  22. # end
  23. #
  24. # If you do not provide a block to #find_each, it will return an Enumerator
  25. # for chaining with other methods:
  26. #
  27. # Person.find_each.with_index do |person, index|
  28. # person.award_trophy(index + 1)
  29. # end
  30. #
  31. # ==== Options
  32. # * <tt>:batch_size</tt> - Specifies the size of the batch. Defaults to 1000.
  33. # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
  34. # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
  35. # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
  36. # an order is present in the relation.
  37. #
  38. # Limits are honored, and if present there is no requirement for the batch
  39. # size: it can be less than, equal to, or greater than the limit.
  40. #
  41. # The options +start+ and +finish+ are especially useful if you want
  42. # multiple workers dealing with the same processing queue. You can make
  43. # worker 1 handle all the records between id 1 and 9999 and worker 2
  44. # handle from 10000 and beyond by setting the +:start+ and +:finish+
  45. # option on each worker.
  46. #
  47. # # In worker 1, let's process until 9999 records.
  48. # Person.find_each(finish: 9_999) do |person|
  49. # person.party_all_night!
  50. # end
  51. #
  52. # # In worker 2, let's process from record 10_000 and onwards.
  53. # Person.find_each(start: 10_000) do |person|
  54. # person.party_all_night!
  55. # end
  56. #
  57. # NOTE: It's not possible to set the order. That is automatically set to
  58. # ascending on the primary key ("id ASC") to make the batch ordering
  59. # work. This also means that this method only works when the primary key is
  60. # orderable (e.g. an integer or string).
  61. #
  62. # NOTE: By its nature, batch processing is subject to race conditions if
  63. # other processes are modifying the database.
  64. 1 def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
  65. if block_given?
  66. find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do |records|
  67. records.each { |record| yield record }
  68. end
  69. else
  70. enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
  71. relation = self
  72. apply_limits(relation, start, finish).size
  73. end
  74. end
  75. end
  76. # Yields each batch of records that was found by the find options as
  77. # an array.
  78. #
  79. # Person.where("age > 21").find_in_batches do |group|
  80. # sleep(50) # Make sure it doesn't get too crowded in there!
  81. # group.each { |person| person.party_all_night! }
  82. # end
  83. #
  84. # If you do not provide a block to #find_in_batches, it will return an Enumerator
  85. # for chaining with other methods:
  86. #
  87. # Person.find_in_batches.with_index do |group, batch|
  88. # puts "Processing group ##{batch}"
  89. # group.each(&:recover_from_last_night!)
  90. # end
  91. #
  92. # To be yielded each record one by one, use #find_each instead.
  93. #
  94. # ==== Options
  95. # * <tt>:batch_size</tt> - Specifies the size of the batch. Defaults to 1000.
  96. # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
  97. # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
  98. # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
  99. # an order is present in the relation.
  100. #
  101. # Limits are honored, and if present there is no requirement for the batch
  102. # size: it can be less than, equal to, or greater than the limit.
  103. #
  104. # The options +start+ and +finish+ are especially useful if you want
  105. # multiple workers dealing with the same processing queue. You can make
  106. # worker 1 handle all the records between id 1 and 9999 and worker 2
  107. # handle from 10000 and beyond by setting the +:start+ and +:finish+
  108. # option on each worker.
  109. #
  110. # # Let's process from record 10_000 on.
  111. # Person.find_in_batches(start: 10_000) do |group|
  112. # group.each { |person| person.party_all_night! }
  113. # end
  114. #
  115. # NOTE: It's not possible to set the order. That is automatically set to
  116. # ascending on the primary key ("id ASC") to make the batch ordering
  117. # work. This also means that this method only works when the primary key is
  118. # orderable (e.g. an integer or string).
  119. #
  120. # NOTE: By its nature, batch processing is subject to race conditions if
  121. # other processes are modifying the database.
  122. 1 def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
  123. relation = self
  124. unless block_given?
  125. return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
  126. total = apply_limits(relation, start, finish).size
  127. (total - 1).div(batch_size) + 1
  128. end
  129. end
  130. in_batches(of: batch_size, start: start, finish: finish, load: true, error_on_ignore: error_on_ignore) do |batch|
  131. yield batch.to_a
  132. end
  133. end
  134. # Yields ActiveRecord::Relation objects to work with a batch of records.
  135. #
  136. # Person.where("age > 21").in_batches do |relation|
  137. # relation.delete_all
  138. # sleep(10) # Throttle the delete queries
  139. # end
  140. #
  141. # If you do not provide a block to #in_batches, it will return a
  142. # BatchEnumerator which is enumerable.
  143. #
  144. # Person.in_batches.each_with_index do |relation, batch_index|
  145. # puts "Processing relation ##{batch_index}"
  146. # relation.delete_all
  147. # end
  148. #
  149. # Examples of calling methods on the returned BatchEnumerator object:
  150. #
  151. # Person.in_batches.delete_all
  152. # Person.in_batches.update_all(awesome: true)
  153. # Person.in_batches.each_record(&:party_all_night!)
  154. #
  155. # ==== Options
  156. # * <tt>:of</tt> - Specifies the size of the batch. Defaults to 1000.
  157. # * <tt>:load</tt> - Specifies if the relation should be loaded. Defaults to false.
  158. # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
  159. # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
  160. # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
  161. # an order is present in the relation.
  162. #
  163. # Limits are honored, and if present there is no requirement for the batch
  164. # size, it can be less than, equal, or greater than the limit.
  165. #
  166. # The options +start+ and +finish+ are especially useful if you want
  167. # multiple workers dealing with the same processing queue. You can make
  168. # worker 1 handle all the records between id 1 and 9999 and worker 2
  169. # handle from 10000 and beyond by setting the +:start+ and +:finish+
  170. # option on each worker.
  171. #
  172. # # Let's process from record 10_000 on.
  173. # Person.in_batches(start: 10_000).update_all(awesome: true)
  174. #
  175. # An example of calling where query method on the relation:
  176. #
  177. # Person.in_batches.each do |relation|
  178. # relation.update_all('age = age + 1')
  179. # relation.where('age > 21').update_all(should_party: true)
  180. # relation.where('age <= 21').delete_all
  181. # end
  182. #
  183. # NOTE: If you are going to iterate through each record, you should call
  184. # #each_record on the yielded BatchEnumerator:
  185. #
  186. # Person.in_batches.each_record(&:party_all_night!)
  187. #
  188. # NOTE: It's not possible to set the order. That is automatically set to
  189. # ascending on the primary key ("id ASC") to make the batch ordering
  190. # consistent. Therefore the primary key must be orderable, e.g. an integer
  191. # or a string.
  192. #
  193. # NOTE: By its nature, batch processing is subject to race conditions if
  194. # other processes are modifying the database.
  195. 1 def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil)
  196. relation = self
  197. unless block_given?
  198. return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
  199. end
  200. if arel.orders.present?
  201. act_on_ignored_order(error_on_ignore)
  202. end
  203. batch_limit = of
  204. if limit_value
  205. remaining = limit_value
  206. batch_limit = remaining if remaining < batch_limit
  207. end
  208. relation = relation.reorder(batch_order).limit(batch_limit)
  209. relation = apply_limits(relation, start, finish)
  210. relation.skip_query_cache! # Retaining the results in the query cache would undermine the point of batching
  211. batch_relation = relation
  212. loop do
  213. if load
  214. records = batch_relation.records
  215. ids = records.map(&:id)
  216. yielded_relation = where(primary_key => ids)
  217. yielded_relation.load_records(records)
  218. else
  219. ids = batch_relation.pluck(primary_key)
  220. yielded_relation = where(primary_key => ids)
  221. end
  222. break if ids.empty?
  223. primary_key_offset = ids.last
  224. raise ArgumentError.new("Primary key not included in the custom select clause") unless primary_key_offset
  225. yield yielded_relation
  226. break if ids.length < batch_limit
  227. if limit_value
  228. remaining -= ids.length
  229. if remaining == 0
  230. # Saves a useless iteration when the limit is a multiple of the
  231. # batch size.
  232. break
  233. elsif remaining < batch_limit
  234. relation = relation.limit(remaining)
  235. end
  236. end
  237. attr = Relation::QueryAttribute.new(primary_key, primary_key_offset, klass.type_for_attribute(primary_key))
  238. batch_relation = relation.where(arel_attribute(primary_key).gt(Arel::Nodes::BindParam.new(attr)))
  239. end
  240. end
  241. 1 private
  242. 1 def apply_limits(relation, start, finish)
  243. if start
  244. attr = Relation::QueryAttribute.new(primary_key, start, klass.type_for_attribute(primary_key))
  245. relation = relation.where(arel_attribute(primary_key).gteq(Arel::Nodes::BindParam.new(attr)))
  246. end
  247. if finish
  248. attr = Relation::QueryAttribute.new(primary_key, finish, klass.type_for_attribute(primary_key))
  249. relation = relation.where(arel_attribute(primary_key).lteq(Arel::Nodes::BindParam.new(attr)))
  250. end
  251. relation
  252. end
  253. 1 def batch_order
  254. arel_attribute(primary_key).asc
  255. end
  256. 1 def act_on_ignored_order(error_on_ignore)
  257. raise_error = (error_on_ignore.nil? ? klass.error_on_ignored_order : error_on_ignore)
  258. if raise_error
  259. raise ArgumentError.new(ORDER_IGNORE_MESSAGE)
  260. elsif logger
  261. logger.warn(ORDER_IGNORE_MESSAGE)
  262. end
  263. end
  264. end
  265. end

target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/batches/batch_enumerator.rb

42.86% lines covered

21 relevant lines. 9 lines covered and 12 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveRecord
  3. 1 module Batches
  4. 1 class BatchEnumerator
  5. 1 include Enumerable
  6. 1 def initialize(of: 1000, start: nil, finish: nil, relation:) #:nodoc:
  7. @of = of
  8. @relation = relation
  9. @start = start
  10. @finish = finish
  11. end
  12. # Looping through a collection of records from the database (using the
  13. # +all+ method, for example) is very inefficient since it will try to
  14. # instantiate all the objects at once.
  15. #
  16. # In that case, batch processing methods allow you to work with the
  17. # records in batches, thereby greatly reducing memory consumption.
  18. #
  19. # Person.in_batches.each_record do |person|
  20. # person.do_awesome_stuff
  21. # end
  22. #
  23. # Person.where("age > 21").in_batches(of: 10).each_record do |person|
  24. # person.party_all_night!
  25. # end
  26. #
  27. # If you do not provide a block to #each_record, it will return an Enumerator
  28. # for chaining with other methods:
  29. #
  30. # Person.in_batches.each_record.with_index do |person, index|
  31. # person.award_trophy(index + 1)
  32. # end
  33. 1 def each_record
  34. return to_enum(:each_record) unless block_given?
  35. @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true).each do |relation|
  36. relation.records.each { |record| yield record }
  37. end
  38. end
  39. # Delegates #delete_all, #update_all, #destroy_all methods to each batch.
  40. #
  41. # People.in_batches.delete_all
  42. # People.where('age < 10').in_batches.destroy_all
  43. # People.in_batches.update_all('age = age + 1')
  44. 1 [:delete_all, :update_all, :destroy_all].each do |method|
  45. 3 define_method(method) do |*args, &block|
  46. @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false).each do |relation|
  47. relation.send(method, *args, &block)
  48. end
  49. end
  50. end
  51. # Yields an ActiveRecord::Relation object for each batch of records.
  52. #
  53. # Person.in_batches.each do |relation|
  54. # relation.update_all(awesome: true)
  55. # end
  56. 1 def each
  57. enum = @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false)
  58. return enum.each { |relation| yield relation } if block_given?
  59. enum
  60. end
  61. end
  62. end
  63. end

target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/calculations.rb

20.13% lines covered

154 relevant lines. 31 lines covered and 123 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveRecord
  3. 1 module Calculations
  4. # Count the records.
  5. #
  6. # Person.count
  7. # # => the total count of all people
  8. #
  9. # Person.count(:age)
  10. # # => returns the total count of all people whose age is present in database
  11. #
  12. # Person.count(:all)
  13. # # => performs a COUNT(*) (:all is an alias for '*')
  14. #
  15. # Person.distinct.count(:age)
  16. # # => counts the number of different age values
  17. #
  18. # If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group],
  19. # it returns a Hash whose keys represent the aggregated column,
  20. # and the values are the respective amounts:
  21. #
  22. # Person.group(:city).count
  23. # # => { 'Rome' => 5, 'Paris' => 3 }
  24. #
  25. # If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group] for multiple columns, it returns a Hash whose
  26. # keys are an array containing the individual values of each column and the value
  27. # of each key would be the #count.
  28. #
  29. # Article.group(:status, :category).count
  30. # # => {["draft", "business"]=>10, ["draft", "technology"]=>4,
  31. # ["published", "business"]=>0, ["published", "technology"]=>2}
  32. #
  33. # If #count is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
  34. #
  35. # Person.select(:age).count
  36. # # => counts the number of different age values
  37. #
  38. # Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
  39. # between databases. In invalid cases, an error from the database is thrown.
  40. 1 def count(column_name = nil)
  41. if block_given?
  42. unless column_name.nil?
  43. ActiveSupport::Deprecation.warn \
  44. "When `count' is called with a block, it ignores other arguments. " \
  45. "This behavior is now deprecated and will result in an ArgumentError in Rails 6.0."
  46. end
  47. return super()
  48. end
  49. calculate(:count, column_name)
  50. end
  51. # Calculates the average value on a given column. Returns +nil+ if there's
  52. # no row. See #calculate for examples with options.
  53. #
  54. # Person.average(:age) # => 35.8
  55. 1 def average(column_name)
  56. calculate(:average, column_name)
  57. end
  58. # Calculates the minimum value on a given column. The value is returned
  59. # with the same data type of the column, or +nil+ if there's no row. See
  60. # #calculate for examples with options.
  61. #
  62. # Person.minimum(:age) # => 7
  63. 1 def minimum(column_name)
  64. calculate(:minimum, column_name)
  65. end
  66. # Calculates the maximum value on a given column. The value is returned
  67. # with the same data type of the column, or +nil+ if there's no row. See
  68. # #calculate for examples with options.
  69. #
  70. # Person.maximum(:age) # => 93
  71. 1 def maximum(column_name)
  72. calculate(:maximum, column_name)
  73. end
  74. # Calculates the sum of values on a given column. The value is returned
  75. # with the same data type of the column, +0+ if there's no row. See
  76. # #calculate for examples with options.
  77. #
  78. # Person.sum(:age) # => 4562
  79. 1 def sum(column_name = nil)
  80. if block_given?
  81. unless column_name.nil?
  82. ActiveSupport::Deprecation.warn \
  83. "When `sum' is called with a block, it ignores other arguments. " \
  84. "This behavior is now deprecated and will result in an ArgumentError in Rails 6.0."
  85. end
  86. return super()
  87. end
  88. calculate(:sum, column_name)
  89. end
  90. # This calculates aggregate values in the given column. Methods for #count, #sum, #average,
  91. # #minimum, and #maximum have been added as shortcuts.
  92. #
  93. # Person.calculate(:count, :all) # The same as Person.count
  94. # Person.average(:age) # SELECT AVG(age) FROM people...
  95. #
  96. # # Selects the minimum age for any family without any minors
  97. # Person.group(:last_name).having("min(age) > 17").minimum(:age)
  98. #
  99. # Person.sum("2 * age")
  100. #
  101. # There are two basic forms of output:
  102. #
  103. # * Single aggregate value: The single value is type cast to Integer for COUNT, Float
  104. # for AVG, and the given column's type for everything else.
  105. #
  106. # * Grouped values: This returns an ordered hash of the values and groups them. It
  107. # takes either a column name, or the name of a belongs_to association.
  108. #
  109. # values = Person.group('last_name').maximum(:age)
  110. # puts values["Drake"]
  111. # # => 43
  112. #
  113. # drake = Family.find_by(last_name: 'Drake')
  114. # values = Person.group(:family).maximum(:age) # Person belongs_to :family
  115. # puts values[drake]
  116. # # => 43
  117. #
  118. # values.each do |family, max_age|
  119. # ...
  120. # end
  121. 1 def calculate(operation, column_name)
  122. if has_include?(column_name)
  123. relation = apply_join_dependency
  124. if operation.to_s.downcase == "count"
  125. relation.distinct!
  126. # PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
  127. if (column_name == :all || column_name.nil?) && select_values.empty?
  128. relation.order_values = []
  129. end
  130. end
  131. relation.calculate(operation, column_name)
  132. else
  133. perform_calculation(operation, column_name)
  134. end
  135. end
  136. # Use #pluck as a shortcut to select one or more attributes without
  137. # loading a bunch of records just to grab the attributes you want.
  138. #
  139. # Person.pluck(:name)
  140. #
  141. # instead of
  142. #
  143. # Person.all.map(&:name)
  144. #
  145. # Pluck returns an Array of attribute values type-casted to match
  146. # the plucked column names, if they can be deduced. Plucking an SQL fragment
  147. # returns String values by default.
  148. #
  149. # Person.pluck(:name)
  150. # # SELECT people.name FROM people
  151. # # => ['David', 'Jeremy', 'Jose']
  152. #
  153. # Person.pluck(:id, :name)
  154. # # SELECT people.id, people.name FROM people
  155. # # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
  156. #
  157. # Person.distinct.pluck(:role)
  158. # # SELECT DISTINCT role FROM people
  159. # # => ['admin', 'member', 'guest']
  160. #
  161. # Person.where(age: 21).limit(5).pluck(:id)
  162. # # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
  163. # # => [2, 3]
  164. #
  165. # Person.pluck('DATEDIFF(updated_at, created_at)')
  166. # # SELECT DATEDIFF(updated_at, created_at) FROM people
  167. # # => ['0', '27761', '173']
  168. #
  169. # See also #ids.
  170. #
  171. 1 def pluck(*column_names)
  172. 2 if loaded? && (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?
  173. return records.pluck(*column_names)
  174. end
  175. 2 if has_include?(column_names.first)
  176. relation = apply_join_dependency
  177. relation.pluck(*column_names)
  178. else
  179. 2 klass.enforce_raw_sql_whitelist(column_names)
  180. 2 relation = spawn
  181. 2 relation.select_values = column_names
  182. 4 result = skip_query_cache_if_necessary { klass.connection.select_all(relation.arel, nil) }
  183. 2 result.cast_values(klass.attribute_types)
  184. end
  185. end
  186. # Pluck all the ID's for the relation using the table's primary key
  187. #
  188. # Person.ids # SELECT people.id FROM people
  189. # Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.person_id = people.id
  190. 1 def ids
  191. pluck primary_key
  192. end
  193. 1 private
  194. 1 def has_include?(column_name)
  195. 2 eager_loading? || (includes_values.present? && column_name && column_name != :all)
  196. end
  197. 1 def perform_calculation(operation, column_name)
  198. operation = operation.to_s.downcase
  199. # If #count is used with #distinct (i.e. `relation.distinct.count`) it is
  200. # considered distinct.
  201. distinct = distinct_value
  202. if operation == "count"
  203. column_name ||= select_for_count
  204. if column_name == :all
  205. if !distinct
  206. distinct = distinct_select?(select_for_count) if group_values.empty?
  207. elsif group_values.any? || select_values.empty? && order_values.empty?
  208. column_name = primary_key
  209. end
  210. elsif distinct_select?(column_name)
  211. distinct = nil
  212. end
  213. end
  214. if group_values.any?
  215. execute_grouped_calculation(operation, column_name, distinct)
  216. else
  217. execute_simple_calculation(operation, column_name, distinct)
  218. end
  219. end
  220. 1 def distinct_select?(column_name)
  221. column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
  222. end
  223. 1 def aggregate_column(column_name)
  224. return column_name if Arel::Expressions === column_name
  225. if @klass.has_attribute?(column_name) || @klass.attribute_alias?(column_name)
  226. @klass.arel_attribute(column_name)
  227. else
  228. Arel.sql(column_name == :all ? "*" : column_name.to_s)
  229. end
  230. end
  231. 1 def operation_over_aggregate_column(column, operation, distinct)
  232. operation == "count" ? column.count(distinct) : column.send(operation)
  233. end
  234. 1 def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
  235. column_alias = column_name
  236. if operation == "count" && (column_name == :all && distinct || has_limit_or_offset?)
  237. # Shortcut when limit is zero.
  238. return 0 if limit_value == 0
  239. query_builder = build_count_subquery(spawn, column_name, distinct)
  240. else
  241. # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
  242. relation = unscope(:order).distinct!(false)
  243. column = aggregate_column(column_name)
  244. select_value = operation_over_aggregate_column(column, operation, distinct)
  245. if operation == "sum" && distinct
  246. select_value.distinct = true
  247. end
  248. column_alias = select_value.alias
  249. column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
  250. relation.select_values = [select_value]
  251. query_builder = relation.arel
  252. end
  253. result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder, nil) }
  254. row = result.first
  255. value = row && row.values.first
  256. type = result.column_types.fetch(column_alias) do
  257. type_for(column_name)
  258. end
  259. type_cast_calculated_value(value, type, operation)
  260. end
  261. 1 def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
  262. group_attrs = group_values
  263. if group_attrs.first.respond_to?(:to_sym)
  264. association = @klass._reflect_on_association(group_attrs.first)
  265. associated = group_attrs.size == 1 && association && association.belongs_to? # only count belongs_to associations
  266. group_fields = Array(associated ? association.foreign_key : group_attrs)
  267. else
  268. group_fields = group_attrs
  269. end
  270. group_fields = arel_columns(group_fields)
  271. group_aliases = group_fields.map { |field| column_alias_for(field) }
  272. group_columns = group_aliases.zip(group_fields)
  273. if operation == "count" && column_name == :all
  274. aggregate_alias = "count_all"
  275. else
  276. aggregate_alias = column_alias_for([operation, column_name].join(" "))
  277. end
  278. select_values = [
  279. operation_over_aggregate_column(
  280. aggregate_column(column_name),
  281. operation,
  282. distinct).as(aggregate_alias)
  283. ]
  284. select_values += self.select_values unless having_clause.empty?
  285. select_values.concat group_columns.map { |aliaz, field|
  286. if field.respond_to?(:as)
  287. field.as(aliaz)
  288. else
  289. "#{field} AS #{aliaz}"
  290. end
  291. }
  292. relation = except(:group).distinct!(false)
  293. relation.group_values = group_fields
  294. relation.select_values = select_values
  295. calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, nil) }
  296. if association
  297. key_ids = calculated_data.collect { |row| row[group_aliases.first] }
  298. key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
  299. key_records = Hash[key_records.map { |r| [r.id, r] }]
  300. end
  301. Hash[calculated_data.map do |row|
  302. key = group_columns.map { |aliaz, col_name|
  303. type = type_for(col_name) do
  304. calculated_data.column_types.fetch(aliaz, Type.default_value)
  305. end
  306. type_cast_calculated_value(row[aliaz], type)
  307. }
  308. key = key.first if key.size == 1
  309. key = key_records[key] if associated
  310. type = calculated_data.column_types.fetch(aggregate_alias) { type_for(column_name) }
  311. [key, type_cast_calculated_value(row[aggregate_alias], type, operation)]
  312. end]
  313. end
  314. # Converts the given keys to the value that the database adapter returns as
  315. # a usable column name:
  316. #
  317. # column_alias_for("users.id") # => "users_id"
  318. # column_alias_for("sum(id)") # => "sum_id"
  319. # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
  320. # column_alias_for("count(*)") # => "count_all"
  321. 1 def column_alias_for(keys)
  322. if keys.respond_to? :name
  323. keys = "#{keys.relation.name}.#{keys.name}"
  324. end
  325. table_name = keys.to_s.downcase
  326. table_name.gsub!(/\*/, "all")
  327. table_name.gsub!(/\W+/, " ")
  328. table_name.strip!
  329. table_name.gsub!(/ +/, "_")
  330. @klass.connection.table_alias_for(table_name)
  331. end
  332. 1 def type_for(field, &block)
  333. field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
  334. @klass.type_for_attribute(field_name, &block)
  335. end
  336. 1 def type_cast_calculated_value(value, type, operation = nil)
  337. case operation
  338. when "count" then value.to_i
  339. when "sum" then type.deserialize(value || 0)
  340. when "average" then value && value.respond_to?(:to_d) ? value.to_d : value
  341. else type.deserialize(value)
  342. end
  343. end
  344. 1 def select_for_count
  345. if select_values.present?
  346. return select_values.first if select_values.one?
  347. select_values.join(", ")
  348. else
  349. :all
  350. end
  351. end
  352. 1 def build_count_subquery(relation, column_name, distinct)
  353. if column_name == :all
  354. relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
  355. else
  356. column_alias = Arel.sql("count_column")
  357. relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
  358. end
  359. subquery = relation.arel.as(Arel.sql("subquery_for_count"))
  360. select_value = operation_over_aggregate_column(column_alias || Arel.star, "count", false)
  361. Arel::SelectManager.new(subquery).project(select_value)
  362. end
  363. end
  364. end

target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/finder_methods.rb

25.13% lines covered

187 relevant lines. 47 lines covered and 140 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/string/filters"
  3. 1 module ActiveRecord
  4. 1 module FinderMethods
  5. 1 ONE_AS_ONE = "1 AS one"
  6. # Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
  7. # If one or more records can not be found for the requested ids, then RecordNotFound will be raised. If the primary key
  8. # is an integer, find by id coerces its arguments using +to_i+.
  9. #
  10. # Person.find(1) # returns the object for ID = 1
  11. # Person.find("1") # returns the object for ID = 1
  12. # Person.find("31-sarah") # returns the object for ID = 31
  13. # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
  14. # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
  15. # Person.find([1]) # returns an array for the object with ID = 1
  16. # Person.where("administrator = 1").order("created_on DESC").find(1)
  17. #
  18. # NOTE: The returned records are in the same order as the ids you provide.
  19. # If you want the results to be sorted by database, you can use ActiveRecord::QueryMethods#where
  20. # method and provide an explicit ActiveRecord::QueryMethods#order option.
  21. # But ActiveRecord::QueryMethods#where method doesn't raise ActiveRecord::RecordNotFound.
  22. #
  23. # ==== Find with lock
  24. #
  25. # Example for find with a lock: Imagine two concurrent transactions:
  26. # each will read <tt>person.visits == 2</tt>, add 1 to it, and save, resulting
  27. # in two saves of <tt>person.visits = 3</tt>. By locking the row, the second
  28. # transaction has to wait until the first is finished; we get the
  29. # expected <tt>person.visits == 4</tt>.
  30. #
  31. # Person.transaction do
  32. # person = Person.lock(true).find(1)
  33. # person.visits += 1
  34. # person.save!
  35. # end
  36. #
  37. # ==== Variations of #find
  38. #
  39. # Person.where(name: 'Spartacus', rating: 4)
  40. # # returns a chainable list (which can be empty).
  41. #
  42. # Person.find_by(name: 'Spartacus', rating: 4)
  43. # # returns the first item or nil.
  44. #
  45. # Person.find_or_initialize_by(name: 'Spartacus', rating: 4)
  46. # # returns the first item or returns a new instance (requires you call .save to persist against the database).
  47. #
  48. # Person.find_or_create_by(name: 'Spartacus', rating: 4)
  49. # # returns the first item or creates it and returns it.
  50. #
  51. # ==== Alternatives for #find
  52. #
  53. # Person.where(name: 'Spartacus', rating: 4).exists?(conditions = :none)
  54. # # returns a boolean indicating if any record with the given conditions exist.
  55. #
  56. # Person.where(name: 'Spartacus', rating: 4).select("field1, field2, field3")
  57. # # returns a chainable list of instances with only the mentioned fields.
  58. #
  59. # Person.where(name: 'Spartacus', rating: 4).ids
  60. # # returns an Array of ids.
  61. #
  62. # Person.where(name: 'Spartacus', rating: 4).pluck(:field1, :field2)
  63. # # returns an Array of the required fields.
  64. 1 def find(*args)
  65. return super if block_given?
  66. find_with_ids(*args)
  67. end
  68. # Finds the first record matching the specified conditions. There
  69. # is no implied ordering so if order matters, you should specify it
  70. # yourself.
  71. #
  72. # If no record is found, returns <tt>nil</tt>.
  73. #
  74. # Post.find_by name: 'Spartacus', rating: 4
  75. # Post.find_by "published_at < ?", 2.weeks.ago
  76. 1 def find_by(arg, *args)
  77. where(arg, *args).take
  78. rescue ::RangeError
  79. nil
  80. end
  81. # Like #find_by, except that if no record is found, raises
  82. # an ActiveRecord::RecordNotFound error.
  83. 1 def find_by!(arg, *args)
  84. where(arg, *args).take!
  85. rescue ::RangeError
  86. raise RecordNotFound.new("Couldn't find #{@klass.name} with an out of range value",
  87. @klass.name, @klass.primary_key)
  88. end
  89. # Gives a record (or N records if a parameter is supplied) without any implied
  90. # order. The order will depend on the database implementation.
  91. # If an order is supplied it will be respected.
  92. #
  93. # Person.take # returns an object fetched by SELECT * FROM people LIMIT 1
  94. # Person.take(5) # returns 5 objects fetched by SELECT * FROM people LIMIT 5
  95. # Person.where(["name LIKE '%?'", name]).take
  96. 1 def take(limit = nil)
  97. limit ? find_take_with_limit(limit) : find_take
  98. end
  99. # Same as #take but raises ActiveRecord::RecordNotFound if no record
  100. # is found. Note that #take! accepts no arguments.
  101. 1 def take!
  102. take || raise_record_not_found_exception!
  103. end
  104. # Find the first record (or first N records if a parameter is supplied).
  105. # If no order is defined it will order by primary key.
  106. #
  107. # Person.first # returns the first object fetched by SELECT * FROM people ORDER BY people.id LIMIT 1
  108. # Person.where(["user_name = ?", user_name]).first
  109. # Person.where(["user_name = :u", { u: user_name }]).first
  110. # Person.order("created_on DESC").offset(5).first
  111. # Person.first(3) # returns the first three objects fetched by SELECT * FROM people ORDER BY people.id LIMIT 3
  112. #
  113. 1 def first(limit = nil)
  114. if limit
  115. find_nth_with_limit(0, limit)
  116. else
  117. find_nth 0
  118. end
  119. end
  120. # Same as #first but raises ActiveRecord::RecordNotFound if no record
  121. # is found. Note that #first! accepts no arguments.
  122. 1 def first!
  123. first || raise_record_not_found_exception!
  124. end
  125. # Find the last record (or last N records if a parameter is supplied).
  126. # If no order is defined it will order by primary key.
  127. #
  128. # Person.last # returns the last object fetched by SELECT * FROM people
  129. # Person.where(["user_name = ?", user_name]).last
  130. # Person.order("created_on DESC").offset(5).last
  131. # Person.last(3) # returns the last three objects fetched by SELECT * FROM people.
  132. #
  133. # Take note that in that last case, the results are sorted in ascending order:
  134. #
  135. # [#<Person id:2>, #<Person id:3>, #<Person id:4>]
  136. #
  137. # and not:
  138. #
  139. # [#<Person id:4>, #<Person id:3>, #<Person id:2>]
  140. 1 def last(limit = nil)
  141. return find_last(limit) if loaded? || has_limit_or_offset?
  142. result = ordered_relation.limit(limit)
  143. result = result.reverse_order!
  144. limit ? result.reverse : result.first
  145. end
  146. # Same as #last but raises ActiveRecord::RecordNotFound if no record
  147. # is found. Note that #last! accepts no arguments.
  148. 1 def last!
  149. last || raise_record_not_found_exception!
  150. end
  151. # Find the second record.
  152. # If no order is defined it will order by primary key.
  153. #
  154. # Person.second # returns the second object fetched by SELECT * FROM people
  155. # Person.offset(3).second # returns the second object from OFFSET 3 (which is OFFSET 4)
  156. # Person.where(["user_name = :u", { u: user_name }]).second
  157. 1 def second
  158. find_nth 1
  159. end
  160. # Same as #second but raises ActiveRecord::RecordNotFound if no record
  161. # is found.
  162. 1 def second!
  163. second || raise_record_not_found_exception!
  164. end
  165. # Find the third record.
  166. # If no order is defined it will order by primary key.
  167. #
  168. # Person.third # returns the third object fetched by SELECT * FROM people
  169. # Person.offset(3).third # returns the third object from OFFSET 3 (which is OFFSET 5)
  170. # Person.where(["user_name = :u", { u: user_name }]).third
  171. 1 def third
  172. find_nth 2
  173. end
  174. # Same as #third but raises ActiveRecord::RecordNotFound if no record
  175. # is found.
  176. 1 def third!
  177. third || raise_record_not_found_exception!
  178. end
  179. # Find the fourth record.
  180. # If no order is defined it will order by primary key.
  181. #
  182. # Person.fourth # returns the fourth object fetched by SELECT * FROM people
  183. # Person.offset(3).fourth # returns the fourth object from OFFSET 3 (which is OFFSET 6)
  184. # Person.where(["user_name = :u", { u: user_name }]).fourth
  185. 1 def fourth
  186. find_nth 3
  187. end
  188. # Same as #fourth but raises ActiveRecord::RecordNotFound if no record
  189. # is found.
  190. 1 def fourth!
  191. fourth || raise_record_not_found_exception!
  192. end
  193. # Find the fifth record.
  194. # If no order is defined it will order by primary key.
  195. #
  196. # Person.fifth # returns the fifth object fetched by SELECT * FROM people
  197. # Person.offset(3).fifth # returns the fifth object from OFFSET 3 (which is OFFSET 7)
  198. # Person.where(["user_name = :u", { u: user_name }]).fifth
  199. 1 def fifth
  200. find_nth 4
  201. end
  202. # Same as #fifth but raises ActiveRecord::RecordNotFound if no record
  203. # is found.
  204. 1 def fifth!
  205. fifth || raise_record_not_found_exception!
  206. end
  207. # Find the forty-second record. Also known as accessing "the reddit".
  208. # If no order is defined it will order by primary key.
  209. #
  210. # Person.forty_two # returns the forty-second object fetched by SELECT * FROM people
  211. # Person.offset(3).forty_two # returns the forty-second object from OFFSET 3 (which is OFFSET 44)
  212. # Person.where(["user_name = :u", { u: user_name }]).forty_two
  213. 1 def forty_two
  214. find_nth 41
  215. end
  216. # Same as #forty_two but raises ActiveRecord::RecordNotFound if no record
  217. # is found.
  218. 1 def forty_two!
  219. forty_two || raise_record_not_found_exception!
  220. end
  221. # Find the third-to-last record.
  222. # If no order is defined it will order by primary key.
  223. #
  224. # Person.third_to_last # returns the third-to-last object fetched by SELECT * FROM people
  225. # Person.offset(3).third_to_last # returns the third-to-last object from OFFSET 3
  226. # Person.where(["user_name = :u", { u: user_name }]).third_to_last
  227. 1 def third_to_last
  228. find_nth_from_last 3
  229. end
  230. # Same as #third_to_last but raises ActiveRecord::RecordNotFound if no record
  231. # is found.
  232. 1 def third_to_last!
  233. third_to_last || raise_record_not_found_exception!
  234. end
  235. # Find the second-to-last record.
  236. # If no order is defined it will order by primary key.
  237. #
  238. # Person.second_to_last # returns the second-to-last object fetched by SELECT * FROM people
  239. # Person.offset(3).second_to_last # returns the second-to-last object from OFFSET 3
  240. # Person.where(["user_name = :u", { u: user_name }]).second_to_last
  241. 1 def second_to_last
  242. find_nth_from_last 2
  243. end
  244. # Same as #second_to_last but raises ActiveRecord::RecordNotFound if no record
  245. # is found.
  246. 1 def second_to_last!
  247. second_to_last || raise_record_not_found_exception!
  248. end
  249. # Returns true if a record exists in the table that matches the +id+ or
  250. # conditions given, or false otherwise. The argument can take six forms:
  251. #
  252. # * Integer - Finds the record with this primary key.
  253. # * String - Finds the record with a primary key corresponding to this
  254. # string (such as <tt>'5'</tt>).
  255. # * Array - Finds the record that matches these +find+-style conditions
  256. # (such as <tt>['name LIKE ?', "%#{query}%"]</tt>).
  257. # * Hash - Finds the record that matches these +find+-style conditions
  258. # (such as <tt>{name: 'David'}</tt>).
  259. # * +false+ - Returns always +false+.
  260. # * No args - Returns +false+ if the relation is empty, +true+ otherwise.
  261. #
  262. # For more information about specifying conditions as a hash or array,
  263. # see the Conditions section in the introduction to ActiveRecord::Base.
  264. #
  265. # Note: You can't pass in a condition as a string (like <tt>name =
  266. # 'Jamie'</tt>), since it would be sanitized and then queried against
  267. # the primary key column, like <tt>id = 'name = \'Jamie\''</tt>.
  268. #
  269. # Person.exists?(5)
  270. # Person.exists?('5')
  271. # Person.exists?(['name LIKE ?', "%#{query}%"])
  272. # Person.exists?(id: [1, 4, 8])
  273. # Person.exists?(name: 'David')
  274. # Person.exists?(false)
  275. # Person.exists?
  276. # Person.where(name: 'Spartacus', rating: 4).exists?
  277. 1 def exists?(conditions = :none)
  278. if Base === conditions
  279. raise ArgumentError, <<-MSG.squish
  280. You are passing an instance of ActiveRecord::Base to `exists?`.
  281. Please pass the id of the object by calling `.id`.
  282. MSG
  283. end
  284. return false if !conditions || limit_value == 0
  285. if eager_loading?
  286. relation = apply_join_dependency(eager_loading: false)
  287. return relation.exists?(conditions)
  288. end
  289. relation = construct_relation_for_exists(conditions)
  290. skip_query_cache_if_necessary { connection.select_one(relation.arel, "#{name} Exists") } ? true : false
  291. rescue ::RangeError
  292. false
  293. end
  294. # This method is called whenever no records are found with either a single
  295. # id or multiple ids and raises an ActiveRecord::RecordNotFound exception.
  296. #
  297. # The error message is different depending on whether a single id or
  298. # multiple ids are provided. If multiple ids are provided, then the number
  299. # of results obtained should be provided in the +result_size+ argument and
  300. # the expected number of results should be provided in the +expected_size+
  301. # argument.
  302. 1 def raise_record_not_found_exception!(ids = nil, result_size = nil, expected_size = nil, key = primary_key, not_found_ids = nil) # :nodoc:
  303. conditions = arel.where_sql(@klass)
  304. conditions = " [#{conditions}]" if conditions
  305. name = @klass.name
  306. if ids.nil?
  307. error = "Couldn't find #{name}".dup
  308. error << " with#{conditions}" if conditions
  309. raise RecordNotFound.new(error, name, key)
  310. elsif Array(ids).size == 1
  311. error = "Couldn't find #{name} with '#{key}'=#{ids}#{conditions}"
  312. raise RecordNotFound.new(error, name, key, ids)
  313. else
  314. error = "Couldn't find all #{name.pluralize} with '#{key}': ".dup
  315. error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})."
  316. error << " Couldn't find #{name.pluralize(not_found_ids.size)} with #{key.to_s.pluralize(not_found_ids.size)} #{not_found_ids.join(', ')}." if not_found_ids
  317. raise RecordNotFound.new(error, name, key, ids)
  318. end
  319. end
  320. 1 private
  321. 1 def offset_index
  322. offset_value || 0
  323. end
  324. 1 def construct_relation_for_exists(conditions)
  325. if distinct_value && offset_value
  326. relation = limit(1)
  327. else
  328. relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1)
  329. end
  330. case conditions
  331. when Array, Hash
  332. relation.where!(conditions) unless conditions.empty?
  333. else
  334. relation.where!(primary_key => conditions) unless conditions == :none
  335. end
  336. relation
  337. end
  338. 1 def construct_join_dependency
  339. including = eager_load_values + includes_values
  340. ActiveRecord::Associations::JoinDependency.new(
  341. klass, table, including
  342. )
  343. end
  344. 1 def apply_join_dependency(eager_loading: group_values.empty?)
  345. join_dependency = construct_join_dependency
  346. relation = except(:includes, :eager_load, :preload).joins!(join_dependency)
  347. if eager_loading && !using_limitable_reflections?(join_dependency.reflections)
  348. if has_limit_or_offset?
  349. limited_ids = limited_ids_for(relation)
  350. limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids)
  351. end
  352. relation.limit_value = relation.offset_value = nil
  353. end
  354. if block_given?
  355. yield relation, join_dependency
  356. else
  357. relation
  358. end
  359. end
  360. 1 def limited_ids_for(relation)
  361. values = @klass.connection.columns_for_distinct(
  362. connection.column_name_from_arel_node(arel_attribute(primary_key)),
  363. relation.order_values
  364. )
  365. relation = relation.except(:select).select(values).distinct!
  366. id_rows = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, "SQL") }
  367. id_rows.map { |row| row[primary_key] }
  368. end
  369. 1 def using_limitable_reflections?(reflections)
  370. reflections.none?(&:collection?)
  371. end
  372. 1 def find_with_ids(*ids)
  373. raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
  374. expects_array = ids.first.kind_of?(Array)
  375. return [] if expects_array && ids.first.empty?
  376. ids = ids.flatten.compact.uniq
  377. model_name = @klass.name
  378. case ids.size
  379. when 0
  380. error_message = "Couldn't find #{model_name} without an ID"
  381. raise RecordNotFound.new(error_message, model_name, primary_key)
  382. when 1
  383. result = find_one(ids.first)
  384. expects_array ? [ result ] : result
  385. else
  386. find_some(ids)
  387. end
  388. rescue ::RangeError
  389. error_message = "Couldn't find #{model_name} with an out of range ID"
  390. raise RecordNotFound.new(error_message, model_name, primary_key, ids)
  391. end
  392. 1 def find_one(id)
  393. if ActiveRecord::Base === id
  394. raise ArgumentError, <<-MSG.squish
  395. You are passing an instance of ActiveRecord::Base to `find`.
  396. Please pass the id of the object by calling `.id`.
  397. MSG
  398. end
  399. relation = where(primary_key => id)
  400. record = relation.take
  401. raise_record_not_found_exception!(id, 0, 1) unless record
  402. record
  403. end
  404. 1 def find_some(ids)
  405. return find_some_ordered(ids) unless order_values.present?
  406. result = where(primary_key => ids).to_a
  407. expected_size =
  408. if limit_value && ids.size > limit_value
  409. limit_value
  410. else
  411. ids.size
  412. end
  413. # 11 ids with limit 3, offset 9 should give 2 results.
  414. if offset_value && (ids.size - offset_value < expected_size)
  415. expected_size = ids.size - offset_value
  416. end
  417. if result.size == expected_size
  418. result
  419. else
  420. raise_record_not_found_exception!(ids, result.size, expected_size)
  421. end
  422. end
  423. 1 def find_some_ordered(ids)
  424. ids = ids.slice(offset_value || 0, limit_value || ids.size) || []
  425. result = except(:limit, :offset).where(primary_key => ids).records
  426. if result.size == ids.size
  427. pk_type = @klass.type_for_attribute(primary_key)
  428. records_by_id = result.index_by(&:id)
  429. ids.map { |id| records_by_id.fetch(pk_type.cast(id)) }
  430. else
  431. raise_record_not_found_exception!(ids, result.size, ids.size)
  432. end
  433. end
  434. 1 def find_take
  435. if loaded?
  436. records.first
  437. else
  438. @take ||= limit(1).records.first
  439. end
  440. end
  441. 1 def find_take_with_limit(limit)
  442. if loaded?
  443. records.take(limit)
  444. else
  445. limit(limit).to_a
  446. end
  447. end
  448. 1 def find_nth(index)
  449. @offsets[offset_index + index] ||= find_nth_with_limit(index, 1).first
  450. end
  451. 1 def find_nth_with_limit(index, limit)
  452. if loaded?
  453. records[index, limit] || []
  454. else
  455. relation = ordered_relation
  456. if limit_value
  457. limit = [limit_value - index, limit].min
  458. end
  459. if limit > 0
  460. relation = relation.offset(offset_index + index) unless index.zero?
  461. relation.limit(limit).to_a
  462. else
  463. []
  464. end
  465. end
  466. end
  467. 1 def find_nth_from_last(index)
  468. if loaded?
  469. records[-index]
  470. else
  471. relation = ordered_relation
  472. if equal?(relation) || has_limit_or_offset?
  473. relation.records[-index]
  474. else
  475. relation.last(index)[-index]
  476. end
  477. end
  478. end
  479. 1 def find_last(limit)
  480. limit ? records.last(limit) : records.last
  481. end
  482. 1 def ordered_relation
  483. if order_values.empty? && primary_key
  484. order(arel_attribute(primary_key).asc)
  485. else
  486. self
  487. end
  488. end
  489. end
  490. end

target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/from_clause.rb

92.31% lines covered

13 relevant lines. 12 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveRecord
  3. 1 class Relation
  4. 1 class FromClause # :nodoc:
  5. 1 attr_reader :value, :name
  6. 1 def initialize(value, name)
  7. 1 @value = value
  8. 1 @name = name
  9. end
  10. 1 def merge(other)
  11. self
  12. end
  13. 1 def empty?
  14. 3 value.nil?
  15. end
  16. 1 def self.empty
  17. 1 @empty ||= new(nil, nil)
  18. end
  19. end
  20. end
  21. end

target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/merger.rb

23.91% lines covered

92 relevant lines. 22 lines covered and 70 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/hash/keys"
  3. 1 module ActiveRecord
  4. 1 class Relation
  5. 1 class HashMerger # :nodoc:
  6. 1 attr_reader :relation, :hash
  7. 1 def initialize(relation, hash)
  8. hash.assert_valid_keys(*Relation::VALUE_METHODS)
  9. @relation = relation
  10. @hash = hash
  11. end
  12. 1 def merge #:nodoc:
  13. Merger.new(relation, other).merge
  14. end
  15. # Applying values to a relation has some side effects. E.g.
  16. # interpolation might take place for where values. So we should
  17. # build a relation to merge in rather than directly merging
  18. # the values.
  19. 1 def other
  20. other = Relation.create(
  21. relation.klass,
  22. table: relation.table,
  23. predicate_builder: relation.predicate_builder
  24. )
  25. hash.each { |k, v|
  26. if k == :joins
  27. if Hash === v
  28. other.joins!(v)
  29. else
  30. other.joins!(*v)
  31. end
  32. elsif k == :select
  33. other._select!(v)
  34. else
  35. other.send("#{k}!", v)
  36. end
  37. }
  38. other
  39. end
  40. end
  41. 1 class Merger # :nodoc:
  42. 1 attr_reader :relation, :values, :other
  43. 1 def initialize(relation, other)
  44. @relation = relation
  45. @values = other.values
  46. @other = other
  47. end
  48. 1 NORMAL_VALUES = Relation::VALUE_METHODS -
  49. Relation::CLAUSE_METHODS -
  50. [:includes, :preload, :joins, :left_outer_joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc:
  51. 1 def normal_values
  52. NORMAL_VALUES
  53. end
  54. 1 def merge
  55. normal_values.each do |name|
  56. value = values[name]
  57. # The unless clause is here mostly for performance reasons (since the `send` call might be moderately
  58. # expensive), most of the time the value is going to be `nil` or `.blank?`, the only catch is that
  59. # `false.blank?` returns `true`, so there needs to be an extra check so that explicit `false` values
  60. # don't fall through the cracks.
  61. unless value.nil? || (value.blank? && false != value)
  62. if name == :select
  63. relation._select!(*value)
  64. else
  65. relation.send("#{name}!", *value)
  66. end
  67. end
  68. end
  69. merge_multi_values
  70. merge_single_values
  71. merge_clauses
  72. merge_preloads
  73. merge_joins
  74. merge_outer_joins
  75. relation
  76. end
  77. 1 private
  78. 1 def merge_preloads
  79. return if other.preload_values.empty? && other.includes_values.empty?
  80. if other.klass == relation.klass
  81. relation.preload!(*other.preload_values) unless other.preload_values.empty?
  82. relation.includes!(other.includes_values) unless other.includes_values.empty?
  83. else
  84. reflection = relation.klass.reflect_on_all_associations.find do |r|
  85. r.class_name == other.klass.name
  86. end || return
  87. unless other.preload_values.empty?
  88. relation.preload! reflection.name => other.preload_values
  89. end
  90. unless other.includes_values.empty?
  91. relation.includes! reflection.name => other.includes_values
  92. end
  93. end
  94. end
  95. 1 def merge_joins
  96. return if other.joins_values.blank?
  97. if other.klass == relation.klass
  98. relation.joins!(*other.joins_values)
  99. else
  100. joins_dependency = other.joins_values.map do |join|
  101. case join
  102. when Hash, Symbol, Array
  103. ActiveRecord::Associations::JoinDependency.new(
  104. other.klass, other.table, join
  105. )
  106. else
  107. join
  108. end
  109. end
  110. relation.joins!(*joins_dependency)
  111. end
  112. end
  113. 1 def merge_outer_joins
  114. return if other.left_outer_joins_values.blank?
  115. if other.klass == relation.klass
  116. relation.left_outer_joins!(*other.left_outer_joins_values)
  117. else
  118. joins_dependency = other.left_outer_joins_values.map do |join|
  119. case join
  120. when Hash, Symbol, Array
  121. ActiveRecord::Associations::JoinDependency.new(
  122. other.klass, other.table, join
  123. )
  124. else
  125. join
  126. end
  127. end
  128. relation.left_outer_joins!(*joins_dependency)
  129. end
  130. end
  131. 1 def merge_multi_values
  132. if other.reordering_value
  133. # override any order specified in the original relation
  134. relation.reorder!(*other.order_values)
  135. elsif other.order_values.any?
  136. # merge in order_values from relation
  137. relation.order!(*other.order_values)
  138. end
  139. extensions = other.extensions - relation.extensions
  140. relation.extending!(*extensions) if extensions.any?
  141. end
  142. 1 def merge_single_values
  143. relation.lock_value ||= other.lock_value if other.lock_value
  144. unless other.create_with_value.blank?
  145. relation.create_with_value = (relation.create_with_value || {}).merge(other.create_with_value)
  146. end
  147. end
  148. 1 def merge_clauses
  149. relation.from_clause = other.from_clause if replace_from_clause?
  150. where_clause = relation.where_clause.merge(other.where_clause)
  151. relation.where_clause = where_clause unless where_clause.empty?
  152. having_clause = relation.having_clause.merge(other.having_clause)
  153. relation.having_clause = having_clause unless having_clause.empty?
  154. end
  155. 1 def replace_from_clause?
  156. relation.from_clause.empty? && !other.from_clause.empty? &&
  157. relation.klass.base_class == other.klass.base_class
  158. end
  159. end
  160. end
  161. end

target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder.rb

62.2% lines covered

82 relevant lines. 51 lines covered and 31 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveRecord
  3. 1 class PredicateBuilder # :nodoc:
  4. 1 delegate :resolve_column_aliases, to: :table
  5. 1 def initialize(table)
  6. 2 @table = table
  7. 2 @handlers = []
  8. 2 register_handler(BasicObject, BasicObjectHandler.new(self))
  9. 2 register_handler(Base, BaseHandler.new(self))
  10. 2 register_handler(Range, RangeHandler.new(self))
  11. 2 register_handler(Relation, RelationHandler.new)
  12. 2 register_handler(Array, ArrayHandler.new(self))
  13. 2 register_handler(Set, ArrayHandler.new(self))
  14. end
  15. 1 def build_from_hash(attributes)
  16. 1 attributes = convert_dot_notation_to_hash(attributes)
  17. 1 expand_from_hash(attributes)
  18. end
  19. 1 def self.references(attributes)
  20. 1 attributes.map do |key, value|
  21. 1 if value.is_a?(Hash)
  22. key
  23. else
  24. 1 key = key.to_s
  25. 1 key.split(".".freeze).first if key.include?(".".freeze)
  26. end
  27. end.compact
  28. end
  29. # Define how a class is converted to Arel nodes when passed to +where+.
  30. # The handler can be any object that responds to +call+, and will be used
  31. # for any value that +===+ the class given. For example:
  32. #
  33. # MyCustomDateRange = Struct.new(:start, :end)
  34. # handler = proc do |column, range|
  35. # Arel::Nodes::Between.new(column,
  36. # Arel::Nodes::And.new([range.start, range.end])
  37. # )
  38. # end
  39. # ActiveRecord::PredicateBuilder.new("users").register_handler(MyCustomDateRange, handler)
  40. 1 def register_handler(klass, handler)
  41. 12 @handlers.unshift([klass, handler])
  42. end
  43. 1 def build(attribute, value)
  44. 1 if table.type(attribute.name).force_equality?(value)
  45. bind = build_bind_attribute(attribute.name, value)
  46. attribute.eq(bind)
  47. else
  48. 1 handler_for(value).call(attribute, value)
  49. end
  50. end
  51. 1 def build_bind_attribute(column_name, value)
  52. 1 attr = Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name))
  53. 1 Arel::Nodes::BindParam.new(attr)
  54. end
  55. 1 protected
  56. 1 attr_reader :table
  57. 1 def expand_from_hash(attributes)
  58. 1 return ["1=0"] if attributes.empty?
  59. 1 attributes.flat_map do |key, value|
  60. 1 if value.is_a?(Hash) && !table.has_column?(key)
  61. associated_predicate_builder(key).expand_from_hash(value)
  62. elsif table.associated_with?(key)
  63. # Find the foreign key when using queries such as:
  64. # Post.where(author: author)
  65. #
  66. # For polymorphic relationships, find the foreign key and type:
  67. # PriceEstimate.where(estimate_of: treasure)
  68. associated_table = table.associated_table(key)
  69. if associated_table.polymorphic_association?
  70. case value.is_a?(Array) ? value.first : value
  71. when Base, Relation
  72. value = [value] unless value.is_a?(Array)
  73. klass = PolymorphicArrayValue
  74. end
  75. end
  76. klass ||= AssociationQueryValue
  77. queries = klass.new(associated_table, value).queries.map do |query|
  78. expand_from_hash(query).reduce(&:and)
  79. end
  80. queries.reduce(&:or)
  81. elsif table.aggregated_with?(key)
  82. mapping = table.reflect_on_aggregation(key).mapping
  83. values = value.nil? ? [nil] : Array.wrap(value)
  84. if mapping.length == 1 || values.empty?
  85. column_name, aggr_attr = mapping.first
  86. values = values.map do |object|
  87. object.respond_to?(aggr_attr) ? object.public_send(aggr_attr) : object
  88. end
  89. build(table.arel_attribute(column_name), values)
  90. else
  91. queries = values.map do |object|
  92. mapping.map do |field_attr, aggregate_attr|
  93. build(table.arel_attribute(field_attr), object.try!(aggregate_attr))
  94. end.reduce(&:and)
  95. end
  96. queries.reduce(&:or)
  97. end
  98. else
  99. 1 build(table.arel_attribute(key), value)
  100. end
  101. end
  102. end
  103. 1 private
  104. 1 def associated_predicate_builder(association_name)
  105. self.class.new(table.associated_table(association_name))
  106. end
  107. 1 def convert_dot_notation_to_hash(attributes)
  108. 1 dot_notation = attributes.select do |k, v|
  109. 1 k.include?(".".freeze) && !v.is_a?(Hash)
  110. end
  111. 1 dot_notation.each_key do |key|
  112. table_name, column_name = key.split(".".freeze)
  113. value = attributes.delete(key)
  114. attributes[table_name] ||= {}
  115. attributes[table_name] = attributes[table_name].merge(column_name => value)
  116. end
  117. 1 attributes
  118. end
  119. 1 def handler_for(object)
  120. 7 @handlers.detect { |klass, _| klass === object }.last
  121. end
  122. end
  123. end
  124. 1 require "active_record/relation/predicate_builder/array_handler"
  125. 1 require "active_record/relation/predicate_builder/base_handler"
  126. 1 require "active_record/relation/predicate_builder/basic_object_handler"
  127. 1 require "active_record/relation/predicate_builder/range_handler"
  128. 1 require "active_record/relation/predicate_builder/relation_handler"
  129. 1 require "active_record/relation/predicate_builder/association_query_value"
  130. 1 require "active_record/relation/predicate_builder/polymorphic_array_value"

target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/array_handler.rb

38.46% lines covered

26 relevant lines. 10 lines covered and 16 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveRecord
  3. 1 class PredicateBuilder
  4. 1 class ArrayHandler # :nodoc:
  5. 1 def initialize(predicate_builder)
  6. 4 @predicate_builder = predicate_builder
  7. end
  8. 1 def call(attribute, value)
  9. return attribute.in([]) if value.empty?
  10. values = value.map { |x| x.is_a?(Base) ? x.id : x }
  11. nils, values = values.partition(&:nil?)
  12. ranges, values = values.partition { |v| v.is_a?(Range) }
  13. values_predicate =
  14. case values.length
  15. when 0 then NullPredicate
  16. when 1 then predicate_builder.build(attribute, values.first)
  17. else
  18. values.map! do |v|
  19. predicate_builder.build_bind_attribute(attribute.name, v)
  20. end
  21. values.empty? ? NullPredicate : attribute.in(values)
  22. end
  23. unless nils.empty?
  24. values_predicate = values_predicate.or(predicate_builder.build(attribute, nil))
  25. end
  26. array_predicates = ranges.map { |range| predicate_builder.build(attribute, range) }
  27. array_predicates.unshift(values_predicate)
  28. array_predicates.inject(&:or)
  29. end
  30. 1 protected
  31. 1 attr_reader :predicate_builder
  32. 1 module NullPredicate # :nodoc:
  33. 1 def self.or(other)
  34. other
  35. end
  36. end
  37. end
  38. end
  39. end

target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/association_query_value.rb

50.0% lines covered

22 relevant lines. 11 lines covered and 11 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveRecord
  3. 1 class PredicateBuilder
  4. 1 class AssociationQueryValue # :nodoc:
  5. 1 def initialize(associated_table, value)
  6. @associated_table = associated_table
  7. @value = value
  8. end
  9. 1 def queries
  10. [associated_table.association_join_foreign_key.to_s => ids]
  11. end
  12. # TODO Change this to private once we've dropped Ruby 2.2 support.
  13. # Workaround for Ruby 2.2 "private attribute?" warning.
  14. 1 protected
  15. 1 attr_reader :associated_table, :value
  16. 1 private
  17. 1 def ids
  18. case value
  19. when Relation
  20. value.select_values.empty? ? value.select(primary_key) : value
  21. when Array
  22. value.map { |v| convert_to_id(v) }
  23. else
  24. convert_to_id(value)
  25. end
  26. end
  27. 1 def primary_key
  28. associated_table.association_join_primary_key
  29. end
  30. 1 def convert_to_id(value)
  31. case value
  32. when Base
  33. value._read_attribute(primary_key)
  34. else
  35. value
  36. end
  37. end
  38. end
  39. end
  40. end

target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/base_handler.rb

88.89% lines covered

9 relevant lines. 8 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveRecord
  3. 1 class PredicateBuilder
  4. 1 class BaseHandler # :nodoc:
  5. 1 def initialize(predicate_builder)
  6. 2 @predicate_builder = predicate_builder
  7. end
  8. 1 def call(attribute, value)
  9. predicate_builder.build(attribute, value.id)
  10. end
  11. 1 protected
  12. 1 attr_reader :predicate_builder
  13. end
  14. end
  15. end

target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/basic_object_handler.rb

100.0% lines covered

10 relevant lines. 10 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveRecord
  3. 1 class PredicateBuilder
  4. 1 class BasicObjectHandler # :nodoc:
  5. 1 def initialize(predicate_builder)
  6. 2 @predicate_builder = predicate_builder
  7. end
  8. 1 def call(attribute, value)
  9. 1 bind = predicate_builder.build_bind_attribute(attribute.name, value)
  10. 1 attribute.eq(bind)
  11. end
  12. 1 protected
  13. 1 attr_reader :predicate_builder
  14. end
  15. end
  16. end

target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb

46.15% lines covered

26 relevant lines. 12 lines covered and 14 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveRecord
  3. 1 class PredicateBuilder
  4. 1 class PolymorphicArrayValue # :nodoc:
  5. 1 def initialize(associated_table, values)
  6. @associated_table = associated_table
  7. @values = values
  8. end
  9. 1 def queries
  10. type_to_ids_mapping.map do |type, ids|
  11. {
  12. associated_table.association_foreign_type.to_s => type,
  13. associated_table.association_foreign_key.to_s => ids
  14. }
  15. end
  16. end
  17. # TODO Change this to private once we've dropped Ruby 2.2 support.
  18. # Workaround for Ruby 2.2 "private attribute?" warning.
  19. 1 protected
  20. 1 attr_reader :associated_table, :values
  21. 1 private
  22. 1 def type_to_ids_mapping
  23. default_hash = Hash.new { |hsh, key| hsh[key] = [] }
  24. values.each_with_object(default_hash) do |value, hash|
  25. hash[klass(value).polymorphic_name] << convert_to_id(value)
  26. end
  27. end
  28. 1 def primary_key(value)
  29. associated_table.association_join_primary_key(klass(value))
  30. end
  31. 1 def klass(value)
  32. case value
  33. when Base
  34. value.class
  35. when Relation
  36. value.klass
  37. end
  38. end
  39. 1 def convert_to_id(value)
  40. case value
  41. when Base
  42. value._read_attribute(primary_key(value))
  43. when Relation
  44. value.select(primary_key(value))
  45. end
  46. end
  47. end
  48. end
  49. end

target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/range_handler.rb

47.62% lines covered

21 relevant lines. 10 lines covered and 11 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveRecord
  3. 1 class PredicateBuilder
  4. 1 class RangeHandler # :nodoc:
  5. 1 class RangeWithBinds < Struct.new(:begin, :end)
  6. 1 def exclude_end?
  7. false
  8. end
  9. end
  10. 1 def initialize(predicate_builder)
  11. 2 @predicate_builder = predicate_builder
  12. end
  13. 1 def call(attribute, value)
  14. begin_bind = predicate_builder.build_bind_attribute(attribute.name, value.begin)
  15. end_bind = predicate_builder.build_bind_attribute(attribute.name, value.end)
  16. if begin_bind.value.infinity?
  17. if end_bind.value.infinity?
  18. attribute.not_in([])
  19. elsif value.exclude_end?
  20. attribute.lt(end_bind)
  21. else
  22. attribute.lteq(end_bind)
  23. end
  24. elsif end_bind.value.infinity?
  25. attribute.gteq(begin_bind)
  26. elsif value.exclude_end?
  27. attribute.gteq(begin_bind).and(attribute.lt(end_bind))
  28. else
  29. attribute.between(RangeWithBinds.new(begin_bind, end_bind))
  30. end
  31. end
  32. 1 protected
  33. 1 attr_reader :predicate_builder
  34. end
  35. end
  36. end

target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/relation_handler.rb

44.44% lines covered

9 relevant lines. 4 lines covered and 5 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveRecord
  3. 1 class PredicateBuilder
  4. 1 class RelationHandler # :nodoc:
  5. 1 def call(attribute, value)
  6. if value.eager_loading?
  7. value = value.send(:apply_join_dependency)
  8. end
  9. if value.select_values.empty?
  10. value = value.select(value.arel_attribute(value.klass.primary_key))
  11. end
  12. attribute.in(value.arel)
  13. end
  14. end
  15. end
  16. end

target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/query_attribute.rb

69.57% lines covered

23 relevant lines. 16 lines covered and 7 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_model/attribute"
  3. 1 module ActiveRecord
  4. 1 class Relation
  5. 1 class QueryAttribute < ActiveModel::Attribute # :nodoc:
  6. 1 def type_cast(value)
  7. 3 value
  8. end
  9. 1 def value_for_database
  10. 4 @value_for_database ||= super
  11. end
  12. 1 def with_cast_value(value)
  13. 2 QueryAttribute.new(name, value, type)
  14. end
  15. 1 def nil?
  16. 1 unless value_before_type_cast.is_a?(StatementCache::Substitute)
  17. value_before_type_cast.nil? ||
  18. type.respond_to?(:subtype, true) && value_for_database.nil?
  19. end
  20. end
  21. 1 def boundable?
  22. return @_boundable if defined?(@_boundable)
  23. value_for_database unless value_before_type_cast.is_a?(StatementCache::Substitute)
  24. @_boundable = true
  25. rescue ::RangeError
  26. @_boundable = false
  27. end
  28. 1 def infinity?
  29. _infinity?(value_before_type_cast) || boundable? && _infinity?(value_for_database)
  30. end
  31. 1 private
  32. 1 def _infinity?(value)
  33. value.respond_to?(:infinite?) && value.infinite?
  34. end
  35. end
  36. end
  37. end

target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/query_methods.rb

45.78% lines covered

391 relevant lines. 179 lines covered and 212 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_record/relation/from_clause"
  3. 1 require "active_record/relation/query_attribute"
  4. 1 require "active_record/relation/where_clause"
  5. 1 require "active_record/relation/where_clause_factory"
  6. 1 require "active_model/forbidden_attributes_protection"
  7. 1 module ActiveRecord
  8. 1 module QueryMethods
  9. 1 extend ActiveSupport::Concern
  10. 1 include ActiveModel::ForbiddenAttributesProtection
  11. # WhereChain objects act as placeholder for queries in which #where does not have any parameter.
  12. # In this case, #where must be chained with #not to return a new relation.
  13. 1 class WhereChain
  14. 1 include ActiveModel::ForbiddenAttributesProtection
  15. 1 def initialize(scope)
  16. @scope = scope
  17. end
  18. # Returns a new relation expressing WHERE + NOT condition according to
  19. # the conditions in the arguments.
  20. #
  21. # #not accepts conditions as a string, array, or hash. See QueryMethods#where for
  22. # more details on each format.
  23. #
  24. # User.where.not("name = 'Jon'")
  25. # # SELECT * FROM users WHERE NOT (name = 'Jon')
  26. #
  27. # User.where.not(["name = ?", "Jon"])
  28. # # SELECT * FROM users WHERE NOT (name = 'Jon')
  29. #
  30. # User.where.not(name: "Jon")
  31. # # SELECT * FROM users WHERE name != 'Jon'
  32. #
  33. # User.where.not(name: nil)
  34. # # SELECT * FROM users WHERE name IS NOT NULL
  35. #
  36. # User.where.not(name: %w(Ko1 Nobu))
  37. # # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
  38. #
  39. # User.where.not(name: "Jon", role: "admin")
  40. # # SELECT * FROM users WHERE name != 'Jon' AND role != 'admin'
  41. 1 def not(opts, *rest)
  42. opts = sanitize_forbidden_attributes(opts)
  43. where_clause = @scope.send(:where_clause_factory).build(opts, rest)
  44. @scope.references!(PredicateBuilder.references(opts)) if Hash === opts
  45. @scope.where_clause += where_clause.invert
  46. @scope
  47. end
  48. end
  49. 1 FROZEN_EMPTY_ARRAY = [].freeze
  50. 1 FROZEN_EMPTY_HASH = {}.freeze
  51. 1 Relation::VALUE_METHODS.each do |name|
  52. 23 method_name = \
  53. case name
  54. 11 when *Relation::MULTI_VALUE_METHODS then "#{name}_values"
  55. 9 when *Relation::SINGLE_VALUE_METHODS then "#{name}_value"
  56. 3 when *Relation::CLAUSE_METHODS then "#{name}_clause"
  57. end
  58. 23 class_eval <<-CODE, __FILE__, __LINE__ + 1
  59. 23 def #{method_name} # def includes_values
  60. 23 get_value(#{name.inspect}) # get_value(:includes)
  61. end # end
  62. 23 def #{method_name}=(value) # def includes_values=(value)
  63. 23 set_value(#{name.inspect}, value) # set_value(:includes, value)
  64. end # end
  65. CODE
  66. end
  67. 1 alias extensions extending_values
  68. # Specify relationships to be included in the result set. For
  69. # example:
  70. #
  71. # users = User.includes(:address)
  72. # users.each do |user|
  73. # user.address.city
  74. # end
  75. #
  76. # allows you to access the +address+ attribute of the +User+ model without
  77. # firing an additional query. This will often result in a
  78. # performance improvement over a simple join.
  79. #
  80. # You can also specify multiple relationships, like this:
  81. #
  82. # users = User.includes(:address, :friends)
  83. #
  84. # Loading nested relationships is possible using a Hash:
  85. #
  86. # users = User.includes(:address, friends: [:address, :followers])
  87. #
  88. # === conditions
  89. #
  90. # If you want to add conditions to your included models you'll have
  91. # to explicitly reference them. For example:
  92. #
  93. # User.includes(:posts).where('posts.name = ?', 'example')
  94. #
  95. # Will throw an error, but this will work:
  96. #
  97. # User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
  98. #
  99. # Note that #includes works with association names while #references needs
  100. # the actual table name.
  101. 1 def includes(*args)
  102. check_if_method_has_arguments!(:includes, args)
  103. spawn.includes!(*args)
  104. end
  105. 1 def includes!(*args) # :nodoc:
  106. args.reject!(&:blank?)
  107. args.flatten!
  108. self.includes_values |= args
  109. self
  110. end
  111. # Forces eager loading by performing a LEFT OUTER JOIN on +args+:
  112. #
  113. # User.eager_load(:posts)
  114. # # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ...
  115. # # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
  116. # # "users"."id"
  117. 1 def eager_load(*args)
  118. check_if_method_has_arguments!(:eager_load, args)
  119. spawn.eager_load!(*args)
  120. end
  121. 1 def eager_load!(*args) # :nodoc:
  122. self.eager_load_values += args
  123. self
  124. end
  125. # Allows preloading of +args+, in the same way that #includes does:
  126. #
  127. # User.preload(:posts)
  128. # # SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
  129. 1 def preload(*args)
  130. check_if_method_has_arguments!(:preload, args)
  131. spawn.preload!(*args)
  132. end
  133. 1 def preload!(*args) # :nodoc:
  134. self.preload_values += args
  135. self
  136. end
  137. # Use to indicate that the given +table_names+ are referenced by an SQL string,
  138. # and should therefore be JOINed in any query rather than loaded separately.
  139. # This method only works in conjunction with #includes.
  140. # See #includes for more details.
  141. #
  142. # User.includes(:posts).where("posts.name = 'foo'")
  143. # # Doesn't JOIN the posts table, resulting in an error.
  144. #
  145. # User.includes(:posts).where("posts.name = 'foo'").references(:posts)
  146. # # Query now knows the string references posts, so adds a JOIN
  147. 1 def references(*table_names)
  148. check_if_method_has_arguments!(:references, table_names)
  149. spawn.references!(*table_names)
  150. end
  151. 1 def references!(*table_names) # :nodoc:
  152. 1 table_names.flatten!
  153. 1 table_names.map!(&:to_s)
  154. 1 self.references_values |= table_names
  155. 1 self
  156. end
  157. # Works in two unique ways.
  158. #
  159. # First: takes a block so it can be used just like <tt>Array#select</tt>.
  160. #
  161. # Model.all.select { |m| m.field == value }
  162. #
  163. # This will build an array of objects from the database for the scope,
  164. # converting them into an array and iterating through them using
  165. # <tt>Array#select</tt>.
  166. #
  167. # Second: Modifies the SELECT statement for the query so that only certain
  168. # fields are retrieved:
  169. #
  170. # Model.select(:field)
  171. # # => [#<Model id: nil, field: "value">]
  172. #
  173. # Although in the above example it looks as though this method returns an
  174. # array, it actually returns a relation object and can have other query
  175. # methods appended to it, such as the other methods in ActiveRecord::QueryMethods.
  176. #
  177. # The argument to the method can also be an array of fields.
  178. #
  179. # Model.select(:field, :other_field, :and_one_more)
  180. # # => [#<Model id: nil, field: "value", other_field: "value", and_one_more: "value">]
  181. #
  182. # You can also use one or more strings, which will be used unchanged as SELECT fields.
  183. #
  184. # Model.select('field AS field_one', 'other_field AS field_two')
  185. # # => [#<Model id: nil, field: "value", other_field: "value">]
  186. #
  187. # If an alias was specified, it will be accessible from the resulting objects:
  188. #
  189. # Model.select('field AS field_one').first.field_one
  190. # # => "value"
  191. #
  192. # Accessing attributes of an object that do not have fields retrieved by a select
  193. # except +id+ will throw ActiveModel::MissingAttributeError:
  194. #
  195. # Model.select(:field).first.other_field
  196. # # => ActiveModel::MissingAttributeError: missing attribute: other_field
  197. 1 def select(*fields)
  198. if block_given?
  199. if fields.any?
  200. raise ArgumentError, "`select' with block doesn't take arguments."
  201. end
  202. return super()
  203. end
  204. raise ArgumentError, "Call `select' with at least one field" if fields.empty?
  205. spawn._select!(*fields)
  206. end
  207. 1 def _select!(*fields) # :nodoc:
  208. fields.flatten!
  209. fields.map! do |field|
  210. klass.attribute_alias?(field) ? klass.attribute_alias(field).to_sym : field
  211. end
  212. self.select_values += fields
  213. self
  214. end
  215. # Allows to specify a group attribute:
  216. #
  217. # User.group(:name)
  218. # # SELECT "users".* FROM "users" GROUP BY name
  219. #
  220. # Returns an array with distinct records based on the +group+ attribute:
  221. #
  222. # User.select([:id, :name])
  223. # # => [#<User id: 1, name: "Oscar">, #<User id: 2, name: "Oscar">, #<User id: 3, name: "Foo">]
  224. #
  225. # User.group(:name)
  226. # # => [#<User id: 3, name: "Foo", ...>, #<User id: 2, name: "Oscar", ...>]
  227. #
  228. # User.group('name AS grouped_name, age')
  229. # # => [#<User id: 3, name: "Foo", age: 21, ...>, #<User id: 2, name: "Oscar", age: 21, ...>, #<User id: 5, name: "Foo", age: 23, ...>]
  230. #
  231. # Passing in an array of attributes to group by is also supported.
  232. #
  233. # User.select([:id, :first_name]).group(:id, :first_name).first(3)
  234. # # => [#<User id: 1, first_name: "Bill">, #<User id: 2, first_name: "Earl">, #<User id: 3, first_name: "Beto">]
  235. 1 def group(*args)
  236. check_if_method_has_arguments!(:group, args)
  237. spawn.group!(*args)
  238. end
  239. 1 def group!(*args) # :nodoc:
  240. args.flatten!
  241. self.group_values += args
  242. self
  243. end
  244. # Allows to specify an order attribute:
  245. #
  246. # User.order(:name)
  247. # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
  248. #
  249. # User.order(email: :desc)
  250. # # SELECT "users".* FROM "users" ORDER BY "users"."email" DESC
  251. #
  252. # User.order(:name, email: :desc)
  253. # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
  254. #
  255. # User.order('name')
  256. # # SELECT "users".* FROM "users" ORDER BY name
  257. #
  258. # User.order('name DESC')
  259. # # SELECT "users".* FROM "users" ORDER BY name DESC
  260. #
  261. # User.order('name DESC, email')
  262. # # SELECT "users".* FROM "users" ORDER BY name DESC, email
  263. 1 def order(*args)
  264. 2 check_if_method_has_arguments!(:order, args)
  265. 2 spawn.order!(*args)
  266. end
  267. # Same as #order but operates on relation in-place instead of copying.
  268. 1 def order!(*args) # :nodoc:
  269. 2 preprocess_order_args(args)
  270. 2 self.order_values += args
  271. 2 self
  272. end
  273. # Replaces any existing order defined on the relation with the specified order.
  274. #
  275. # User.order('email DESC').reorder('id ASC') # generated SQL has 'ORDER BY id ASC'
  276. #
  277. # Subsequent calls to order on the same relation will be appended. For example:
  278. #
  279. # User.order('email DESC').reorder('id ASC').order('name ASC')
  280. #
  281. # generates a query with 'ORDER BY id ASC, name ASC'.
  282. 1 def reorder(*args)
  283. check_if_method_has_arguments!(:reorder, args)
  284. spawn.reorder!(*args)
  285. end
  286. # Same as #reorder but operates on relation in-place instead of copying.
  287. 1 def reorder!(*args) # :nodoc:
  288. preprocess_order_args(args)
  289. self.reordering_value = true
  290. self.order_values = args
  291. self
  292. end
  293. 1 VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
  294. :limit, :offset, :joins, :left_outer_joins,
  295. :includes, :from, :readonly, :having])
  296. # Removes an unwanted relation that is already defined on a chain of relations.
  297. # This is useful when passing around chains of relations and would like to
  298. # modify the relations without reconstructing the entire chain.
  299. #
  300. # User.order('email DESC').unscope(:order) == User.all
  301. #
  302. # The method arguments are symbols which correspond to the names of the methods
  303. # which should be unscoped. The valid arguments are given in VALID_UNSCOPING_VALUES.
  304. # The method can also be called with multiple arguments. For example:
  305. #
  306. # User.order('email DESC').select('id').where(name: "John")
  307. # .unscope(:order, :select, :where) == User.all
  308. #
  309. # One can additionally pass a hash as an argument to unscope specific +:where+ values.
  310. # This is done by passing a hash with a single key-value pair. The key should be
  311. # +:where+ and the value should be the where value to unscope. For example:
  312. #
  313. # User.where(name: "John", active: true).unscope(where: :name)
  314. # == User.where(active: true)
  315. #
  316. # This method is similar to #except, but unlike
  317. # #except, it persists across merges:
  318. #
  319. # User.order('email').merge(User.except(:order))
  320. # == User.order('email')
  321. #
  322. # User.order('email').merge(User.unscope(:order))
  323. # == User.all
  324. #
  325. # This means it can be used in association definitions:
  326. #
  327. # has_many :comments, -> { unscope(where: :trashed) }
  328. #
  329. 1 def unscope(*args)
  330. check_if_method_has_arguments!(:unscope, args)
  331. spawn.unscope!(*args)
  332. end
  333. 1 def unscope!(*args) # :nodoc:
  334. args.flatten!
  335. self.unscope_values += args
  336. args.each do |scope|
  337. case scope
  338. when Symbol
  339. scope = :left_outer_joins if scope == :left_joins
  340. if !VALID_UNSCOPING_VALUES.include?(scope)
  341. raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
  342. end
  343. set_value(scope, DEFAULT_VALUES[scope])
  344. when Hash
  345. scope.each do |key, target_value|
  346. if key != :where
  347. raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key."
  348. end
  349. target_values = Array(target_value).map(&:to_s)
  350. self.where_clause = where_clause.except(*target_values)
  351. end
  352. else
  353. raise ArgumentError, "Unrecognized scoping: #{args.inspect}. Use .unscope(where: :attribute_name) or .unscope(:order), for example."
  354. end
  355. end
  356. self
  357. end
  358. # Performs a joins on +args+. The given symbol(s) should match the name of
  359. # the association(s).
  360. #
  361. # User.joins(:posts)
  362. # # SELECT "users".*
  363. # # FROM "users"
  364. # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
  365. #
  366. # Multiple joins:
  367. #
  368. # User.joins(:posts, :account)
  369. # # SELECT "users".*
  370. # # FROM "users"
  371. # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
  372. # # INNER JOIN "accounts" ON "accounts"."id" = "users"."account_id"
  373. #
  374. # Nested joins:
  375. #
  376. # User.joins(posts: [:comments])
  377. # # SELECT "users".*
  378. # # FROM "users"
  379. # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
  380. # # INNER JOIN "comments" "comments_posts"
  381. # # ON "comments_posts"."post_id" = "posts"."id"
  382. #
  383. # You can use strings in order to customize your joins:
  384. #
  385. # User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id")
  386. # # SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
  387. 1 def joins(*args)
  388. check_if_method_has_arguments!(:joins, args)
  389. spawn.joins!(*args)
  390. end
  391. 1 def joins!(*args) # :nodoc:
  392. args.compact!
  393. args.flatten!
  394. self.joins_values += args
  395. self
  396. end
  397. # Performs a left outer joins on +args+:
  398. #
  399. # User.left_outer_joins(:posts)
  400. # => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
  401. #
  402. 1 def left_outer_joins(*args)
  403. check_if_method_has_arguments!(__callee__, args)
  404. spawn.left_outer_joins!(*args)
  405. end
  406. 1 alias :left_joins :left_outer_joins
  407. 1 def left_outer_joins!(*args) # :nodoc:
  408. args.compact!
  409. args.flatten!
  410. self.left_outer_joins_values += args
  411. self
  412. end
  413. # Returns a new relation, which is the result of filtering the current relation
  414. # according to the conditions in the arguments.
  415. #
  416. # #where accepts conditions in one of several formats. In the examples below, the resulting
  417. # SQL is given as an illustration; the actual query generated may be different depending
  418. # on the database adapter.
  419. #
  420. # === string
  421. #
  422. # A single string, without additional arguments, is passed to the query
  423. # constructor as an SQL fragment, and used in the where clause of the query.
  424. #
  425. # Client.where("orders_count = '2'")
  426. # # SELECT * from clients where orders_count = '2';
  427. #
  428. # Note that building your own string from user input may expose your application
  429. # to injection attacks if not done properly. As an alternative, it is recommended
  430. # to use one of the following methods.
  431. #
  432. # === array
  433. #
  434. # If an array is passed, then the first element of the array is treated as a template, and
  435. # the remaining elements are inserted into the template to generate the condition.
  436. # Active Record takes care of building the query to avoid injection attacks, and will
  437. # convert from the ruby type to the database type where needed. Elements are inserted
  438. # into the string in the order in which they appear.
  439. #
  440. # User.where(["name = ? and email = ?", "Joe", "joe@example.com"])
  441. # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
  442. #
  443. # Alternatively, you can use named placeholders in the template, and pass a hash as the
  444. # second element of the array. The names in the template are replaced with the corresponding
  445. # values from the hash.
  446. #
  447. # User.where(["name = :name and email = :email", { name: "Joe", email: "joe@example.com" }])
  448. # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
  449. #
  450. # This can make for more readable code in complex queries.
  451. #
  452. # Lastly, you can use sprintf-style % escapes in the template. This works slightly differently
  453. # than the previous methods; you are responsible for ensuring that the values in the template
  454. # are properly quoted. The values are passed to the connector for quoting, but the caller
  455. # is responsible for ensuring they are enclosed in quotes in the resulting SQL. After quoting,
  456. # the values are inserted using the same escapes as the Ruby core method +Kernel::sprintf+.
  457. #
  458. # User.where(["name = '%s' and email = '%s'", "Joe", "joe@example.com"])
  459. # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
  460. #
  461. # If #where is called with multiple arguments, these are treated as if they were passed as
  462. # the elements of a single array.
  463. #
  464. # User.where("name = :name and email = :email", { name: "Joe", email: "joe@example.com" })
  465. # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
  466. #
  467. # When using strings to specify conditions, you can use any operator available from
  468. # the database. While this provides the most flexibility, you can also unintentionally introduce
  469. # dependencies on the underlying database. If your code is intended for general consumption,
  470. # test with multiple database backends.
  471. #
  472. # === hash
  473. #
  474. # #where will also accept a hash condition, in which the keys are fields and the values
  475. # are values to be searched for.
  476. #
  477. # Fields can be symbols or strings. Values can be single values, arrays, or ranges.
  478. #
  479. # User.where({ name: "Joe", email: "joe@example.com" })
  480. # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'
  481. #
  482. # User.where({ name: ["Alice", "Bob"]})
  483. # # SELECT * FROM users WHERE name IN ('Alice', 'Bob')
  484. #
  485. # User.where({ created_at: (Time.now.midnight - 1.day)..Time.now.midnight })
  486. # # SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000')
  487. #
  488. # In the case of a belongs_to relationship, an association key can be used
  489. # to specify the model if an ActiveRecord object is used as the value.
  490. #
  491. # author = Author.find(1)
  492. #
  493. # # The following queries will be equivalent:
  494. # Post.where(author: author)
  495. # Post.where(author_id: author)
  496. #
  497. # This also works with polymorphic belongs_to relationships:
  498. #
  499. # treasure = Treasure.create(name: 'gold coins')
  500. # treasure.price_estimates << PriceEstimate.create(price: 125)
  501. #
  502. # # The following queries will be equivalent:
  503. # PriceEstimate.where(estimate_of: treasure)
  504. # PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
  505. #
  506. # === Joins
  507. #
  508. # If the relation is the result of a join, you may create a condition which uses any of the
  509. # tables in the join. For string and array conditions, use the table name in the condition.
  510. #
  511. # User.joins(:posts).where("posts.created_at < ?", Time.now)
  512. #
  513. # For hash conditions, you can either use the table name in the key, or use a sub-hash.
  514. #
  515. # User.joins(:posts).where({ "posts.published" => true })
  516. # User.joins(:posts).where({ posts: { published: true } })
  517. #
  518. # === no argument
  519. #
  520. # If no argument is passed, #where returns a new instance of WhereChain, that
  521. # can be chained with #not to return a new relation that negates the where clause.
  522. #
  523. # User.where.not(name: "Jon")
  524. # # SELECT * FROM users WHERE name != 'Jon'
  525. #
  526. # See WhereChain for more details on #not.
  527. #
  528. # === blank condition
  529. #
  530. # If the condition is any blank-ish object, then #where is a no-op and returns
  531. # the current relation.
  532. 1 def where(opts = :chain, *rest)
  533. 1 if :chain == opts
  534. WhereChain.new(spawn)
  535. elsif opts.blank?
  536. self
  537. else
  538. 1 spawn.where!(opts, *rest)
  539. end
  540. end
  541. 1 def where!(opts, *rest) # :nodoc:
  542. 1 opts = sanitize_forbidden_attributes(opts)
  543. 1 references!(PredicateBuilder.references(opts)) if Hash === opts
  544. 1 self.where_clause += where_clause_factory.build(opts, rest)
  545. 1 self
  546. end
  547. # Allows you to change a previously set where condition for a given attribute, instead of appending to that condition.
  548. #
  549. # Post.where(trashed: true).where(trashed: false)
  550. # # WHERE `trashed` = 1 AND `trashed` = 0
  551. #
  552. # Post.where(trashed: true).rewhere(trashed: false)
  553. # # WHERE `trashed` = 0
  554. #
  555. # Post.where(active: true).where(trashed: true).rewhere(trashed: false)
  556. # # WHERE `active` = 1 AND `trashed` = 0
  557. #
  558. # This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
  559. # Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
  560. 1 def rewhere(conditions)
  561. unscope(where: conditions.keys).where(conditions)
  562. end
  563. # Returns a new relation, which is the logical union of this relation and the one passed as an
  564. # argument.
  565. #
  566. # The two relations must be structurally compatible: they must be scoping the same model, and
  567. # they must differ only by #where (if no #group has been defined) or #having (if a #group is
  568. # present). Neither relation may have a #limit, #offset, or #distinct set.
  569. #
  570. # Post.where("id = 1").or(Post.where("author_id = 3"))
  571. # # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))
  572. #
  573. 1 def or(other)
  574. unless other.is_a? Relation
  575. raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
  576. end
  577. spawn.or!(other)
  578. end
  579. 1 def or!(other) # :nodoc:
  580. incompatible_values = structurally_incompatible_values_for_or(other)
  581. unless incompatible_values.empty?
  582. raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
  583. end
  584. self.where_clause = self.where_clause.or(other.where_clause)
  585. self.having_clause = having_clause.or(other.having_clause)
  586. self.references_values += other.references_values
  587. self
  588. end
  589. # Allows to specify a HAVING clause. Note that you can't use HAVING
  590. # without also specifying a GROUP clause.
  591. #
  592. # Order.having('SUM(price) > 30').group('user_id')
  593. 1 def having(opts, *rest)
  594. opts.blank? ? self : spawn.having!(opts, *rest)
  595. end
  596. 1 def having!(opts, *rest) # :nodoc:
  597. opts = sanitize_forbidden_attributes(opts)
  598. references!(PredicateBuilder.references(opts)) if Hash === opts
  599. self.having_clause += having_clause_factory.build(opts, rest)
  600. self
  601. end
  602. # Specifies a limit for the number of records to retrieve.
  603. #
  604. # User.limit(10) # generated SQL has 'LIMIT 10'
  605. #
  606. # User.limit(10).limit(20) # generated SQL has 'LIMIT 20'
  607. 1 def limit(value)
  608. 1 spawn.limit!(value)
  609. end
  610. 1 def limit!(value) # :nodoc:
  611. 1 self.limit_value = value
  612. 1 self
  613. end
  614. # Specifies the number of rows to skip before returning rows.
  615. #
  616. # User.offset(10) # generated SQL has "OFFSET 10"
  617. #
  618. # Should be used with order.
  619. #
  620. # User.offset(10).order("name ASC")
  621. 1 def offset(value)
  622. spawn.offset!(value)
  623. end
  624. 1 def offset!(value) # :nodoc:
  625. self.offset_value = value
  626. self
  627. end
  628. # Specifies locking settings (default to +true+). For more information
  629. # on locking, please see ActiveRecord::Locking.
  630. 1 def lock(locks = true)
  631. spawn.lock!(locks)
  632. end
  633. 1 def lock!(locks = true) # :nodoc:
  634. case locks
  635. when String, TrueClass, NilClass
  636. self.lock_value = locks || true
  637. else
  638. self.lock_value = false
  639. end
  640. self
  641. end
  642. # Returns a chainable relation with zero records.
  643. #
  644. # The returned relation implements the Null Object pattern. It is an
  645. # object with defined null behavior and always returns an empty array of
  646. # records without querying the database.
  647. #
  648. # Any subsequent condition chained to the returned relation will continue
  649. # generating an empty relation and will not fire any query to the database.
  650. #
  651. # Used in cases where a method or scope could return zero records but the
  652. # result needs to be chainable.
  653. #
  654. # For example:
  655. #
  656. # @posts = current_user.visible_posts.where(name: params[:name])
  657. # # the visible_posts method is expected to return a chainable Relation
  658. #
  659. # def visible_posts
  660. # case role
  661. # when 'Country Manager'
  662. # Post.where(country: country)
  663. # when 'Reviewer'
  664. # Post.published
  665. # when 'Bad User'
  666. # Post.none # It can't be chained if [] is returned.
  667. # end
  668. # end
  669. #
  670. 1 def none
  671. spawn.none!
  672. end
  673. 1 def none! # :nodoc:
  674. where!("1=0").extending!(NullRelation)
  675. end
  676. # Sets readonly attributes for the returned relation. If value is
  677. # true (default), attempting to update a record will result in an error.
  678. #
  679. # users = User.readonly
  680. # users.first.save
  681. # => ActiveRecord::ReadOnlyRecord: User is marked as readonly
  682. 1 def readonly(value = true)
  683. spawn.readonly!(value)
  684. end
  685. 1 def readonly!(value = true) # :nodoc:
  686. self.readonly_value = value
  687. self
  688. end
  689. # Sets attributes to be used when creating new records from a
  690. # relation object.
  691. #
  692. # users = User.where(name: 'Oscar')
  693. # users.new.name # => 'Oscar'
  694. #
  695. # users = users.create_with(name: 'DHH')
  696. # users.new.name # => 'DHH'
  697. #
  698. # You can pass +nil+ to #create_with to reset attributes:
  699. #
  700. # users = users.create_with(nil)
  701. # users.new.name # => 'Oscar'
  702. 1 def create_with(value)
  703. spawn.create_with!(value)
  704. end
  705. 1 def create_with!(value) # :nodoc:
  706. if value
  707. value = sanitize_forbidden_attributes(value)
  708. self.create_with_value = create_with_value.merge(value)
  709. else
  710. self.create_with_value = FROZEN_EMPTY_HASH
  711. end
  712. self
  713. end
  714. # Specifies table from which the records will be fetched. For example:
  715. #
  716. # Topic.select('title').from('posts')
  717. # # SELECT title FROM posts
  718. #
  719. # Can accept other relation objects. For example:
  720. #
  721. # Topic.select('title').from(Topic.approved)
  722. # # SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
  723. #
  724. # Topic.select('a.title').from(Topic.approved, :a)
  725. # # SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
  726. #
  727. 1 def from(value, subquery_name = nil)
  728. spawn.from!(value, subquery_name)
  729. end
  730. 1 def from!(value, subquery_name = nil) # :nodoc:
  731. self.from_clause = Relation::FromClause.new(value, subquery_name)
  732. self
  733. end
  734. # Specifies whether the records should be unique or not. For example:
  735. #
  736. # User.select(:name)
  737. # # Might return two records with the same name
  738. #
  739. # User.select(:name).distinct
  740. # # Returns 1 record per distinct name
  741. #
  742. # User.select(:name).distinct.distinct(false)
  743. # # You can also remove the uniqueness
  744. 1 def distinct(value = true)
  745. spawn.distinct!(value)
  746. end
  747. # Like #distinct, but modifies relation in place.
  748. 1 def distinct!(value = true) # :nodoc:
  749. self.distinct_value = value
  750. self
  751. end
  752. # Used to extend a scope with additional methods, either through
  753. # a module or through a block provided.
  754. #
  755. # The object returned is a relation, which can be further extended.
  756. #
  757. # === Using a module
  758. #
  759. # module Pagination
  760. # def page(number)
  761. # # pagination code goes here
  762. # end
  763. # end
  764. #
  765. # scope = Model.all.extending(Pagination)
  766. # scope.page(params[:page])
  767. #
  768. # You can also pass a list of modules:
  769. #
  770. # scope = Model.all.extending(Pagination, SomethingElse)
  771. #
  772. # === Using a block
  773. #
  774. # scope = Model.all.extending do
  775. # def page(number)
  776. # # pagination code goes here
  777. # end
  778. # end
  779. # scope.page(params[:page])
  780. #
  781. # You can also use a block and a module list:
  782. #
  783. # scope = Model.all.extending(Pagination) do
  784. # def per_page(number)
  785. # # pagination code goes here
  786. # end
  787. # end
  788. 1 def extending(*modules, &block)
  789. if modules.any? || block
  790. spawn.extending!(*modules, &block)
  791. else
  792. self
  793. end
  794. end
  795. 1 def extending!(*modules, &block) # :nodoc:
  796. modules << Module.new(&block) if block
  797. modules.flatten!
  798. self.extending_values += modules
  799. extend(*extending_values) if extending_values.any?
  800. self
  801. end
  802. # Reverse the existing order clause on the relation.
  803. #
  804. # User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
  805. 1 def reverse_order
  806. spawn.reverse_order!
  807. end
  808. 1 def reverse_order! # :nodoc:
  809. orders = order_values.uniq
  810. orders.reject!(&:blank?)
  811. self.order_values = reverse_sql_order(orders)
  812. self
  813. end
  814. 1 def skip_query_cache!(value = true) # :nodoc:
  815. self.skip_query_cache_value = value
  816. self
  817. end
  818. # Returns the Arel object associated with the relation.
  819. 1 def arel(aliases = nil) # :nodoc:
  820. 3 @arel ||= build_arel(aliases)
  821. end
  822. # Returns a relation value with a given name
  823. 1 def get_value(name) # :nodoc:
  824. 60 @values.fetch(name, DEFAULT_VALUES[name])
  825. end
  826. 1 protected
  827. # Sets the relation value with the given name
  828. 1 def set_value(name, value) # :nodoc:
  829. 7 assert_mutability!
  830. 7 @values[name] = value
  831. end
  832. 1 private
  833. 1 def assert_mutability!
  834. 7 raise ImmutableRelation if @loaded
  835. 7 raise ImmutableRelation if defined?(@arel) && @arel
  836. end
  837. 1 def build_arel(aliases)
  838. 3 arel = Arel::SelectManager.new(table)
  839. 3 aliases = build_joins(arel, joins_values.flatten, aliases) unless joins_values.empty?
  840. 3 build_left_outer_joins(arel, left_outer_joins_values.flatten, aliases) unless left_outer_joins_values.empty?
  841. 3 arel.where(where_clause.ast) unless where_clause.empty?
  842. 3 arel.having(having_clause.ast) unless having_clause.empty?
  843. 3 if limit_value
  844. 2 limit_attribute = ActiveModel::Attribute.with_cast_value(
  845. "LIMIT".freeze,
  846. connection.sanitize_limit(limit_value),
  847. Type.default_value,
  848. )
  849. 1 arel.take(Arel::Nodes::BindParam.new(limit_attribute))
  850. end
  851. 3 if offset_value
  852. offset_attribute = ActiveModel::Attribute.with_cast_value(
  853. "OFFSET".freeze,
  854. offset_value.to_i,
  855. Type.default_value,
  856. )
  857. arel.skip(Arel::Nodes::BindParam.new(offset_attribute))
  858. end
  859. 3 arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty?
  860. 3 build_order(arel)
  861. 3 build_select(arel)
  862. 3 arel.distinct(distinct_value)
  863. 3 arel.from(build_from) unless from_clause.empty?
  864. 3 arel.lock(lock_value) if lock_value
  865. 3 arel
  866. end
  867. 1 def build_from
  868. opts = from_clause.value
  869. name = from_clause.name
  870. case opts
  871. when Relation
  872. if opts.eager_loading?
  873. opts = opts.send(:apply_join_dependency)
  874. end
  875. name ||= "subquery"
  876. opts.arel.as(name.to_s)
  877. else
  878. opts
  879. end
  880. end
  881. 1 def build_left_outer_joins(manager, outer_joins, aliases)
  882. buckets = outer_joins.group_by do |join|
  883. case join
  884. when Hash, Symbol, Array
  885. :association_join
  886. when ActiveRecord::Associations::JoinDependency
  887. :stashed_join
  888. else
  889. raise ArgumentError, "only Hash, Symbol and Array are allowed"
  890. end
  891. end
  892. build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases)
  893. end
  894. 1 def build_joins(manager, joins, aliases)
  895. buckets = joins.group_by do |join|
  896. case join
  897. when String
  898. :string_join
  899. when Hash, Symbol, Array
  900. :association_join
  901. when ActiveRecord::Associations::JoinDependency
  902. :stashed_join
  903. when Arel::Nodes::Join
  904. :join_node
  905. else
  906. raise "unknown class: %s" % join.class.name
  907. end
  908. end
  909. build_join_query(manager, buckets, Arel::Nodes::InnerJoin, aliases)
  910. end
  911. 1 def build_join_query(manager, buckets, join_type, aliases)
  912. buckets.default = []
  913. association_joins = buckets[:association_join]
  914. stashed_joins = buckets[:stashed_join]
  915. join_nodes = buckets[:join_node].uniq
  916. string_joins = buckets[:string_join].map(&:strip).uniq
  917. join_list = join_nodes + convert_join_strings_to_ast(string_joins)
  918. alias_tracker = alias_tracker(join_list, aliases)
  919. join_dependency = ActiveRecord::Associations::JoinDependency.new(
  920. klass, table, association_joins
  921. )
  922. joins = join_dependency.join_constraints(stashed_joins, join_type, alias_tracker)
  923. joins.each { |join| manager.from(join) }
  924. manager.join_sources.concat(join_list)
  925. alias_tracker.aliases
  926. end
  927. 1 def convert_join_strings_to_ast(joins)
  928. joins
  929. .flatten
  930. .reject(&:blank?)
  931. .map { |join| table.create_string_join(Arel.sql(join)) }
  932. end
  933. 1 def build_select(arel)
  934. 3 if select_values.any?
  935. 2 arel.project(*arel_columns(select_values.uniq))
  936. elsif klass.ignored_columns.any?
  937. arel.project(*klass.column_names.map { |field| arel_attribute(field) })
  938. else
  939. 1 arel.project(table[Arel.star])
  940. end
  941. end
  942. 1 def arel_columns(columns)
  943. 2 columns.flat_map do |field|
  944. 2 case field
  945. when Symbol
  946. 2 field = field.to_s
  947. 2 arel_column(field) { connection.quote_table_name(field) }
  948. when String
  949. arel_column(field) { field }
  950. when Proc
  951. field.call
  952. else
  953. field
  954. end
  955. end
  956. end
  957. 1 def arel_column(field)
  958. 4 field = klass.attribute_alias(field) if klass.attribute_alias?(field)
  959. 4 from = from_clause.name || from_clause.value
  960. 4 if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
  961. 4 arel_attribute(field)
  962. else
  963. yield
  964. end
  965. end
  966. 1 def table_name_matches?(from)
  967. /(?:\A|(?<!FROM)\s)(?:\b#{table.name}\b|#{connection.quote_table_name(table.name)})(?!\.)/i.match?(from.to_s)
  968. end
  969. 1 def reverse_sql_order(order_query)
  970. if order_query.empty?
  971. return [arel_attribute(primary_key).desc] if primary_key
  972. raise IrreversibleOrderError,
  973. "Relation has no current order and table has no primary key to be used as default order"
  974. end
  975. order_query.flat_map do |o|
  976. case o
  977. when Arel::Attribute
  978. o.desc
  979. when Arel::Nodes::Ordering
  980. o.reverse
  981. when String
  982. if does_not_support_reverse?(o)
  983. raise IrreversibleOrderError, "Order #{o.inspect} can not be reversed automatically"
  984. end
  985. o.split(",").map! do |s|
  986. s.strip!
  987. s.gsub!(/\sasc\Z/i, " DESC") || s.gsub!(/\sdesc\Z/i, " ASC") || (s << " DESC")
  988. end
  989. else
  990. o
  991. end
  992. end
  993. end
  994. 1 def does_not_support_reverse?(order)
  995. # Account for String subclasses like Arel::Nodes::SqlLiteral that
  996. # override methods like #count.
  997. order = String.new(order) unless order.instance_of?(String)
  998. # Uses SQL function with multiple arguments.
  999. (order.include?(",") && order.split(",").find { |section| section.count("(") != section.count(")") }) ||
  1000. # Uses "nulls first" like construction.
  1001. /nulls (first|last)\Z/i.match?(order)
  1002. end
  1003. 1 def build_order(arel)
  1004. 3 orders = order_values.uniq
  1005. 3 orders.reject!(&:blank?)
  1006. 3 arel.order(*orders) unless orders.empty?
  1007. end
  1008. 1 VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
  1009. "asc", "desc", "ASC", "DESC"].to_set # :nodoc:
  1010. 1 def validate_order_args(args)
  1011. 2 args.each do |arg|
  1012. 2 next unless arg.is_a?(Hash)
  1013. arg.each do |_key, value|
  1014. unless VALID_DIRECTIONS.include?(value)
  1015. raise ArgumentError,
  1016. "Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
  1017. end
  1018. end
  1019. end
  1020. end
  1021. 1 def preprocess_order_args(order_args)
  1022. 2 order_args.map! do |arg|
  1023. 2 klass.sanitize_sql_for_order(arg)
  1024. end
  1025. 2 order_args.flatten!
  1026. 4 @klass.enforce_raw_sql_whitelist(
  1027. 4 order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
  1028. whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST
  1029. )
  1030. 2 validate_order_args(order_args)
  1031. 2 references = order_args.grep(String)
  1032. 2 references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
  1033. 2 references!(references) if references.any?
  1034. # if a symbol is given we prepend the quoted table name
  1035. 2 order_args.map! do |arg|
  1036. 2 case arg
  1037. when Symbol
  1038. 2 arg = arg.to_s
  1039. 2 arel_column(arg) {
  1040. Arel.sql(connection.quote_table_name(arg))
  1041. }.asc
  1042. when Hash
  1043. arg.map { |field, dir|
  1044. case field
  1045. when Arel::Nodes::SqlLiteral
  1046. field.send(dir.downcase)
  1047. else
  1048. field = field.to_s
  1049. arel_column(field) {
  1050. Arel.sql(connection.quote_table_name(field))
  1051. }.send(dir.downcase)
  1052. end
  1053. }
  1054. else
  1055. arg
  1056. end
  1057. end.flatten!
  1058. end
  1059. # Checks to make sure that the arguments are not blank. Note that if some
  1060. # blank-like object were initially passed into the query method, then this
  1061. # method will not raise an error.
  1062. #
  1063. # Example:
  1064. #
  1065. # Post.references() # raises an error
  1066. # Post.references([]) # does not raise an error
  1067. #
  1068. # This particular method should be called with a method_name and the args
  1069. # passed into that method as an input. For example:
  1070. #
  1071. # def references(*args)
  1072. # check_if_method_has_arguments!("references", args)
  1073. # ...
  1074. # end
  1075. 1 def check_if_method_has_arguments!(method_name, args)
  1076. 2 if args.blank?
  1077. raise ArgumentError, "The method .#{method_name}() must contain arguments."
  1078. end
  1079. end
  1080. 1 STRUCTURAL_OR_METHODS = Relation::VALUE_METHODS - [:extending, :where, :having, :unscope, :references]
  1081. 1 def structurally_incompatible_values_for_or(other)
  1082. STRUCTURAL_OR_METHODS.reject do |method|
  1083. get_value(method) == other.get_value(method)
  1084. end
  1085. end
  1086. 1 def where_clause_factory
  1087. 1 @where_clause_factory ||= Relation::WhereClauseFactory.new(klass, predicate_builder)
  1088. end
  1089. 1 alias having_clause_factory where_clause_factory
  1090. 1 DEFAULT_VALUES = {
  1091. create_with: FROZEN_EMPTY_HASH,
  1092. where: Relation::WhereClause.empty,
  1093. having: Relation::WhereClause.empty,
  1094. from: Relation::FromClause.empty
  1095. }
  1096. 1 Relation::MULTI_VALUE_METHODS.each do |value|
  1097. 11 DEFAULT_VALUES[value] ||= FROZEN_EMPTY_ARRAY
  1098. end
  1099. end
  1100. end

target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/spawn_methods.rb

48.15% lines covered

27 relevant lines. 13 lines covered and 14 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/hash/except"
  3. 1 require "active_support/core_ext/hash/slice"
  4. 1 require "active_record/relation/merger"
  5. 1 module ActiveRecord
  6. 1 module SpawnMethods
  7. # This is overridden by Associations::CollectionProxy
  8. 1 def spawn #:nodoc:
  9. 6 @delegate_to_klass ? klass.all : clone
  10. end
  11. # Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an ActiveRecord::Relation.
  12. # Returns an array representing the intersection of the resulting records with <tt>other</tt>, if <tt>other</tt> is an array.
  13. #
  14. # Post.where(published: true).joins(:comments).merge( Comment.where(spam: false) )
  15. # # Performs a single join query with both where conditions.
  16. #
  17. # recent_posts = Post.order('created_at DESC').first(5)
  18. # Post.where(published: true).merge(recent_posts)
  19. # # Returns the intersection of all published posts with the 5 most recently created posts.
  20. # # (This is just an example. You'd probably want to do this with a single query!)
  21. #
  22. # Procs will be evaluated by merge:
  23. #
  24. # Post.where(published: true).merge(-> { joins(:comments) })
  25. # # => Post.where(published: true).joins(:comments)
  26. #
  27. # This is mainly intended for sharing common conditions between multiple associations.
  28. 1 def merge(other)
  29. if other.is_a?(Array)
  30. records & other
  31. elsif other
  32. spawn.merge!(other)
  33. else
  34. raise ArgumentError, "invalid argument: #{other.inspect}."
  35. end
  36. end
  37. 1 def merge!(other) # :nodoc:
  38. if other.is_a?(Hash)
  39. Relation::HashMerger.new(self, other).merge
  40. elsif other.is_a?(Relation)
  41. Relation::Merger.new(self, other).merge
  42. elsif other.respond_to?(:to_proc)
  43. instance_exec(&other)
  44. else
  45. raise ArgumentError, "#{other.inspect} is not an ActiveRecord::Relation"
  46. end
  47. end
  48. # Removes from the query the condition(s) specified in +skips+.
  49. #
  50. # Post.order('id asc').except(:order) # discards the order condition
  51. # Post.where('id > 10').order('id asc').except(:where) # discards the where condition but keeps the order
  52. 1 def except(*skips)
  53. relation_with values.except(*skips)
  54. end
  55. # Removes any condition from the query other than the one(s) specified in +onlies+.
  56. #
  57. # Post.order('id asc').only(:where) # discards the order condition
  58. # Post.order('id asc').only(:where, :order) # uses the specified order
  59. 1 def only(*onlies)
  60. relation_with values.slice(*onlies)
  61. end
  62. 1 private
  63. 1 def relation_with(values)
  64. result = Relation.create(klass, values: values)
  65. result.extend(*extending_values) if extending_values.any?
  66. result
  67. end
  68. end
  69. end

target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/where_clause.rb

40.86% lines covered

93 relevant lines. 38 lines covered and 55 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveRecord
  3. 1 class Relation
  4. 1 class WhereClause # :nodoc:
  5. 1 delegate :any?, :empty?, to: :predicates
  6. 1 def initialize(predicates)
  7. 3 @predicates = predicates
  8. end
  9. 1 def +(other)
  10. 2 WhereClause.new(
  11. predicates + other.predicates,
  12. )
  13. end
  14. 1 def -(other)
  15. WhereClause.new(
  16. predicates - other.predicates,
  17. )
  18. end
  19. 1 def merge(other)
  20. WhereClause.new(
  21. predicates_unreferenced_by(other) + other.predicates,
  22. )
  23. end
  24. 1 def except(*columns)
  25. WhereClause.new(except_predicates(columns))
  26. end
  27. 1 def or(other)
  28. left = self - other
  29. common = self - left
  30. right = other - common
  31. if left.empty? || right.empty?
  32. common
  33. else
  34. or_clause = WhereClause.new(
  35. [left.ast.or(right.ast)],
  36. )
  37. common + or_clause
  38. end
  39. end
  40. 1 def to_h(table_name = nil)
  41. equalities = equalities(predicates)
  42. if table_name
  43. equalities = equalities.select do |node|
  44. node.left.relation.name == table_name
  45. end
  46. end
  47. equalities.map { |node|
  48. name = node.left.name.to_s
  49. value = extract_node_value(node.right)
  50. [name, value]
  51. }.to_h
  52. end
  53. 1 def ast
  54. 1 Arel::Nodes::And.new(predicates_with_wrapped_sql_literals)
  55. end
  56. 1 def ==(other)
  57. other.is_a?(WhereClause) &&
  58. predicates == other.predicates
  59. end
  60. 1 def invert
  61. WhereClause.new(inverted_predicates)
  62. end
  63. 1 def self.empty
  64. 2 @empty ||= new([])
  65. end
  66. 1 protected
  67. 1 attr_reader :predicates
  68. 1 def referenced_columns
  69. @referenced_columns ||= begin
  70. equality_nodes = predicates.select { |n| equality_node?(n) }
  71. Set.new(equality_nodes, &:left)
  72. end
  73. end
  74. 1 private
  75. 1 def equalities(predicates)
  76. equalities = []
  77. predicates.each do |node|
  78. case node
  79. when Arel::Nodes::Equality
  80. equalities << node
  81. when Arel::Nodes::And
  82. equalities.concat equalities(node.children)
  83. end
  84. end
  85. equalities
  86. end
  87. 1 def predicates_unreferenced_by(other)
  88. predicates.reject do |n|
  89. equality_node?(n) && other.referenced_columns.include?(n.left)
  90. end
  91. end
  92. 1 def equality_node?(node)
  93. node.respond_to?(:operator) && node.operator == :==
  94. end
  95. 1 def inverted_predicates
  96. predicates.map { |node| invert_predicate(node) }
  97. end
  98. 1 def invert_predicate(node)
  99. case node
  100. when NilClass
  101. raise ArgumentError, "Invalid argument for .where.not(), got nil."
  102. when Arel::Nodes::In
  103. Arel::Nodes::NotIn.new(node.left, node.right)
  104. when Arel::Nodes::Equality
  105. Arel::Nodes::NotEqual.new(node.left, node.right)
  106. when String
  107. Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(node))
  108. else
  109. Arel::Nodes::Not.new(node)
  110. end
  111. end
  112. 1 def except_predicates(columns)
  113. predicates.reject do |node|
  114. case node
  115. when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual
  116. subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right)
  117. columns.include?(subrelation.name.to_s)
  118. end
  119. end
  120. end
  121. 1 def predicates_with_wrapped_sql_literals
  122. 1 non_empty_predicates.map do |node|
  123. 1 case node
  124. when Arel::Nodes::SqlLiteral, ::String
  125. wrap_sql_literal(node)
  126. 1 else node
  127. end
  128. end
  129. end
  130. 1 ARRAY_WITH_EMPTY_STRING = [""]
  131. 1 def non_empty_predicates
  132. 1 predicates - ARRAY_WITH_EMPTY_STRING
  133. end
  134. 1 def wrap_sql_literal(node)
  135. if ::String === node
  136. node = Arel.sql(node)
  137. end
  138. Arel::Nodes::Grouping.new(node)
  139. end
  140. 1 def extract_node_value(node)
  141. case node
  142. when Array
  143. node.map { |v| extract_node_value(v) }
  144. when Arel::Nodes::Casted, Arel::Nodes::Quoted
  145. node.val
  146. when Arel::Nodes::BindParam
  147. value = node.value
  148. if value.respond_to?(:value_before_type_cast)
  149. value.value_before_type_cast
  150. else
  151. value
  152. end
  153. end
  154. end
  155. end
  156. end
  157. end

target/rubygems/gems/activerecord-5.2.3/lib/active_record/relation/where_clause_factory.rb

82.35% lines covered

17 relevant lines. 14 lines covered and 3 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveRecord
  3. 1 class Relation
  4. 1 class WhereClauseFactory # :nodoc:
  5. 1 def initialize(klass, predicate_builder)
  6. 1 @klass = klass
  7. 1 @predicate_builder = predicate_builder
  8. end
  9. 1 def build(opts, other)
  10. 1 case opts
  11. when String, Array
  12. parts = [klass.sanitize_sql(other.empty? ? opts : ([opts] + other))]
  13. when Hash
  14. 1 attributes = predicate_builder.resolve_column_aliases(opts)
  15. 1 attributes.stringify_keys!
  16. 1 parts = predicate_builder.build_from_hash(attributes)
  17. when Arel::Nodes::Node
  18. parts = [opts]
  19. else
  20. raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
  21. end
  22. 1 WhereClause.new(parts)
  23. end
  24. 1 protected
  25. 1 attr_reader :klass, :predicate_builder
  26. end
  27. end
  28. end

target/rubygems/gems/activerecord-5.2.3/lib/active_record/result.rb

85.71% lines covered

56 relevant lines. 48 lines covered and 8 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveRecord
  3. ###
  4. # This class encapsulates a result returned from calling
  5. # {#exec_query}[rdoc-ref:ConnectionAdapters::DatabaseStatements#exec_query]
  6. # on any database connection adapter. For example:
  7. #
  8. # result = ActiveRecord::Base.connection.exec_query('SELECT id, title, body FROM posts')
  9. # result # => #<ActiveRecord::Result:0xdeadbeef>
  10. #
  11. # # Get the column names of the result:
  12. # result.columns
  13. # # => ["id", "title", "body"]
  14. #
  15. # # Get the record values of the result:
  16. # result.rows
  17. # # => [[1, "title_1", "body_1"],
  18. # [2, "title_2", "body_2"],
  19. # ...
  20. # ]
  21. #
  22. # # Get an array of hashes representing the result (column => value):
  23. # result.to_hash
  24. # # => [{"id" => 1, "title" => "title_1", "body" => "body_1"},
  25. # {"id" => 2, "title" => "title_2", "body" => "body_2"},
  26. # ...
  27. # ]
  28. #
  29. # # ActiveRecord::Result also includes Enumerable.
  30. # result.each do |row|
  31. # puts row['title'] + " " + row['body']
  32. # end
  33. 1 class Result
  34. 1 include Enumerable
  35. 1 attr_reader :columns, :rows, :column_types
  36. 1 def initialize(columns, rows, column_types = {})
  37. 7 @columns = columns
  38. 7 @rows = rows
  39. 7 @hash_rows = nil
  40. 7 @column_types = column_types
  41. end
  42. # Returns the number of elements in the rows array.
  43. 1 def length
  44. 2 @rows.length
  45. end
  46. # Calls the given block once for each element in row collection, passing
  47. # row as parameter.
  48. #
  49. # Returns an +Enumerator+ if no block is given.
  50. 1 def each
  51. 2 if block_given?
  52. 4 hash_rows.each { |row| yield row }
  53. else
  54. hash_rows.to_enum { @rows.size }
  55. end
  56. end
  57. # Returns an array of hashes representing each row record.
  58. 1 def to_hash
  59. hash_rows
  60. end
  61. 1 alias :map! :map
  62. 1 alias :collect! :map
  63. # Returns true if there are no records, otherwise false.
  64. 1 def empty?
  65. rows.empty?
  66. end
  67. # Returns an array of hashes representing each row record.
  68. 1 def to_ary
  69. hash_rows
  70. end
  71. 1 def [](idx)
  72. hash_rows[idx]
  73. end
  74. # Returns the first record from the rows collection.
  75. # If the rows collection is empty, returns +nil+.
  76. 1 def first
  77. 1 return nil if @rows.empty?
  78. 1 Hash[@columns.zip(@rows.first)]
  79. end
  80. # Returns the last record from the rows collection.
  81. # If the rows collection is empty, returns +nil+.
  82. 1 def last
  83. return nil if @rows.empty?
  84. Hash[@columns.zip(@rows.last)]
  85. end
  86. 1 def cast_values(type_overrides = {}) # :nodoc:
  87. 4 types = columns.map { |name| column_type(name, type_overrides) }
  88. 2 result = rows.map do |values|
  89. 8 types.zip(values).map { |type, value| type.deserialize(value) }
  90. end
  91. 2 columns.one? ? result.map!(&:first) : result
  92. end
  93. 1 def initialize_copy(other)
  94. 1 @columns = columns.dup
  95. 1 @rows = rows.dup
  96. 1 @column_types = column_types.dup
  97. 1 @hash_rows = nil
  98. end
  99. 1 private
  100. 1 def column_type(name, type_overrides = {})
  101. 2 type_overrides.fetch(name) do
  102. column_types.fetch(name, Type.default_value)
  103. end
  104. end
  105. 1 def hash_rows
  106. 2 @hash_rows ||=
  107. begin
  108. # We freeze the strings to prevent them getting duped when
  109. # used as keys in ActiveRecord::Base's @attributes hash
  110. 20 columns = @columns.map { |c| c.dup.freeze }
  111. 2 @rows.map { |row|
  112. # In the past we used Hash[columns.zip(row)]
  113. # though elegant, the verbose way is much more efficient
  114. # both time and memory wise cause it avoids a big array allocation
  115. # this method is called a lot and needs to be micro optimised
  116. 2 hash = {}
  117. 2 index = 0
  118. 2 length = columns.length
  119. 2 while index < length
  120. 18 hash[columns[index]] = row[index]
  121. 18 index += 1
  122. end
  123. 2 hash
  124. }
  125. end
  126. end
  127. end
  128. end

target/rubygems/gems/activerecord-5.2.3/lib/active_record/runtime_registry.rb

100.0% lines covered

8 relevant lines. 8 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/per_thread_registry"
  3. 1 module ActiveRecord
  4. # This is a thread locals registry for Active Record. For example:
  5. #
  6. # ActiveRecord::RuntimeRegistry.connection_handler
  7. #
  8. # returns the connection handler local to the current thread.
  9. #
  10. # See the documentation of ActiveSupport::PerThreadRegistry
  11. # for further details.
  12. 1 class RuntimeRegistry # :nodoc:
  13. 1 extend ActiveSupport::PerThreadRegistry
  14. 1 attr_accessor :connection_handler, :sql_runtime
  15. 1 [:connection_handler, :sql_runtime].each do |val|
  16. 2 class_eval %{ def self.#{val}; instance.#{val}; end }, __FILE__, __LINE__
  17. 2 class_eval %{ def self.#{val}=(x); instance.#{val}=x; end }, __FILE__, __LINE__
  18. end
  19. end
  20. end

target/rubygems/gems/activerecord-5.2.3/lib/active_record/schema_migration.rb

65.38% lines covered

26 relevant lines. 17 lines covered and 9 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_record/scoping/default"
  3. 1 require "active_record/scoping/named"
  4. 1 module ActiveRecord
  5. # This class is used to create a table that keeps track of which migrations
  6. # have been applied to a given database. When a migration is run, its schema
  7. # number is inserted in to the `SchemaMigration.table_name` so it doesn't need
  8. # to be executed the next time.
  9. 1 class SchemaMigration < ActiveRecord::Base # :nodoc:
  10. 1 class << self
  11. 1 def primary_key
  12. "version"
  13. end
  14. 1 def table_name
  15. 4 "#{table_name_prefix}#{ActiveRecord::Base.schema_migrations_table_name}#{table_name_suffix}"
  16. end
  17. 1 def table_exists?
  18. 2 connection.table_exists?(table_name)
  19. end
  20. 1 def create_table
  21. unless table_exists?
  22. version_options = connection.internal_string_options_for_primary_key
  23. connection.create_table(table_name, id: false) do |t|
  24. t.string :version, version_options
  25. end
  26. end
  27. end
  28. 1 def drop_table
  29. connection.drop_table table_name, if_exists: true
  30. end
  31. 1 def normalize_migration_number(number)
  32. "%.3d" % number.to_i
  33. end
  34. 1 def normalized_versions
  35. all_versions.map { |v| normalize_migration_number v }
  36. end
  37. 1 def all_versions
  38. 2 order(:version).pluck(:version)
  39. end
  40. end
  41. 1 def version
  42. super.to_i
  43. end
  44. end
  45. end

target/rubygems/gems/activerecord-5.2.3/lib/active_record/statement_cache.rb

83.02% lines covered

53 relevant lines. 44 lines covered and 9 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveRecord
  3. # Statement cache is used to cache a single statement in order to avoid creating the AST again.
  4. # Initializing the cache is done by passing the statement in the create block:
  5. #
  6. # cache = StatementCache.create(Book.connection) do |params|
  7. # Book.where(name: "my book").where("author_id > 3")
  8. # end
  9. #
  10. # The cached statement is executed by using the
  11. # {connection.execute}[rdoc-ref:ConnectionAdapters::DatabaseStatements#execute] method:
  12. #
  13. # cache.execute([], Book.connection)
  14. #
  15. # The relation returned by the block is cached, and for each
  16. # {execute}[rdoc-ref:ConnectionAdapters::DatabaseStatements#execute]
  17. # call the cached relation gets duped. Database is queried when +to_a+ is called on the relation.
  18. #
  19. # If you want to cache the statement without the values you can use the +bind+ method of the
  20. # block parameter.
  21. #
  22. # cache = StatementCache.create(Book.connection) do |params|
  23. # Book.where(name: params.bind)
  24. # end
  25. #
  26. # And pass the bind values as the first argument of +execute+ call.
  27. #
  28. # cache.execute(["my book"], Book.connection)
  29. 1 class StatementCache # :nodoc:
  30. 1 class Substitute; end # :nodoc:
  31. 1 class Query # :nodoc:
  32. 1 def initialize(sql)
  33. 1 @sql = sql
  34. end
  35. 1 def sql_for(binds, connection)
  36. 2 @sql
  37. end
  38. end
  39. 1 class PartialQuery < Query # :nodoc:
  40. 1 def initialize(values)
  41. @values = values
  42. @indexes = values.each_with_index.find_all { |thing, i|
  43. Arel::Nodes::BindParam === thing
  44. }.map(&:last)
  45. end
  46. 1 def sql_for(binds, connection)
  47. val = @values.dup
  48. casted_binds = binds.map(&:value_for_database)
  49. @indexes.each { |i| val[i] = connection.quote(casted_binds.shift) }
  50. val.join
  51. end
  52. end
  53. 1 def self.query(sql)
  54. 1 Query.new(sql)
  55. end
  56. 1 def self.partial_query(values)
  57. PartialQuery.new(values)
  58. end
  59. 1 class Params # :nodoc:
  60. 2 def bind; Substitute.new; end
  61. end
  62. 1 class BindMap # :nodoc:
  63. 1 def initialize(bound_attributes)
  64. 1 @indexes = []
  65. 1 @bound_attributes = bound_attributes
  66. 1 bound_attributes.each_with_index do |attr, i|
  67. 2 if Substitute === attr.value
  68. 1 @indexes << i
  69. end
  70. end
  71. end
  72. 1 def bind(values)
  73. 2 bas = @bound_attributes.dup
  74. 4 @indexes.each_with_index { |offset, i| bas[offset] = bas[offset].with_cast_value(values[i]) }
  75. 2 bas
  76. end
  77. end
  78. 2 def self.create(connection, block = Proc.new)
  79. 1 relation = block.call Params.new
  80. 1 query_builder, binds = connection.cacheable_query(self, relation.arel)
  81. 1 bind_map = BindMap.new(binds)
  82. 1 new(query_builder, bind_map, relation.klass)
  83. end
  84. 1 def initialize(query_builder, bind_map, klass)
  85. 1 @query_builder = query_builder
  86. 1 @bind_map = bind_map
  87. 1 @klass = klass
  88. end
  89. 1 def execute(params, connection, &block)
  90. 2 bind_values = bind_map.bind params
  91. 2 sql = query_builder.sql_for bind_values, connection
  92. 2 klass.find_by_sql(sql, bind_values, preparable: true, &block)
  93. end
  94. 1 def self.unsupported_value?(value)
  95. 2 case value
  96. when NilClass, Array, Range, Hash, Relation, Base then true
  97. end
  98. end
  99. 1 protected
  100. 1 attr_reader :query_builder, :bind_map, :klass
  101. end
  102. end

target/rubygems/gems/activerecord-5.2.3/lib/active_record/table_metadata.rb

67.44% lines covered

43 relevant lines. 29 lines covered and 14 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveRecord
  3. 1 class TableMetadata # :nodoc:
  4. 1 delegate :foreign_type, :foreign_key, :join_primary_key, :join_foreign_key, to: :association, prefix: true
  5. 1 def initialize(klass, arel_table, association = nil)
  6. 2 @klass = klass
  7. 2 @arel_table = arel_table
  8. 2 @association = association
  9. end
  10. 1 def resolve_column_aliases(hash)
  11. 1 new_hash = hash.dup
  12. 1 hash.each do |key, _|
  13. 1 if (key.is_a?(Symbol)) && klass.attribute_alias?(key)
  14. new_hash[klass.attribute_alias(key)] = new_hash.delete(key)
  15. end
  16. end
  17. 1 new_hash
  18. end
  19. 1 def arel_attribute(column_name)
  20. 1 if klass
  21. 1 klass.arel_attribute(column_name, arel_table)
  22. else
  23. arel_table[column_name]
  24. end
  25. end
  26. 1 def type(column_name)
  27. 2 if klass
  28. 2 klass.type_for_attribute(column_name)
  29. else
  30. Type.default_value
  31. end
  32. end
  33. 1 def has_column?(column_name)
  34. klass && klass.columns_hash.key?(column_name.to_s)
  35. end
  36. 1 def associated_with?(association_name)
  37. 1 klass && klass._reflect_on_association(association_name)
  38. end
  39. 1 def associated_table(table_name)
  40. association = klass._reflect_on_association(table_name) || klass._reflect_on_association(table_name.to_s.singularize)
  41. if !association && table_name == arel_table.name
  42. return self
  43. elsif association && !association.polymorphic?
  44. association_klass = association.klass
  45. arel_table = association_klass.arel_table.alias(table_name)
  46. else
  47. type_caster = TypeCaster::Connection.new(klass, table_name)
  48. association_klass = nil
  49. arel_table = Arel::Table.new(table_name, type_caster: type_caster)
  50. end
  51. TableMetadata.new(association_klass, arel_table, association)
  52. end
  53. 1 def polymorphic_association?
  54. association && association.polymorphic?
  55. end
  56. 1 def aggregated_with?(aggregation_name)
  57. 1 klass && reflect_on_aggregation(aggregation_name)
  58. end
  59. 1 def reflect_on_aggregation(aggregation_name)
  60. 1 klass.reflect_on_aggregation(aggregation_name)
  61. end
  62. # TODO Change this to private once we've dropped Ruby 2.2 support.
  63. # Workaround for Ruby 2.2 "private attribute?" warning.
  64. 1 protected
  65. 1 attr_reader :klass, :arel_table, :association
  66. end
  67. end

target/rubygems/gems/activestorage-5.2.3/config/routes.rb

70.59% lines covered

17 relevant lines. 12 lines covered and 5 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 Rails.application.routes.draw do
  3. 1 get "/rails/active_storage/blobs/:signed_id/*filename" => "active_storage/blobs#show", as: :rails_service_blob
  4. 1 direct :rails_blob do |blob, options|
  5. route_for(:rails_service_blob, blob.signed_id, blob.filename, options)
  6. end
  7. 1 resolve("ActiveStorage::Blob") { |blob, options| route_for(:rails_blob, blob, options) }
  8. 1 resolve("ActiveStorage::Attachment") { |attachment, options| route_for(:rails_blob, attachment.blob, options) }
  9. 1 get "/rails/active_storage/representations/:signed_blob_id/:variation_key/*filename" => "active_storage/representations#show", as: :rails_blob_representation
  10. 1 direct :rails_representation do |representation, options|
  11. signed_blob_id = representation.blob.signed_id
  12. variation_key = representation.variation.key
  13. filename = representation.blob.filename
  14. route_for(:rails_blob_representation, signed_blob_id, variation_key, filename, options)
  15. end
  16. 1 resolve("ActiveStorage::Variant") { |variant, options| route_for(:rails_representation, variant, options) }
  17. 1 resolve("ActiveStorage::Preview") { |preview, options| route_for(:rails_representation, preview, options) }
  18. 1 get "/rails/active_storage/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_service
  19. 1 put "/rails/active_storage/disk/:encoded_token" => "active_storage/disk#update", as: :update_rails_disk_service
  20. 1 post "/rails/active_storage/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads
  21. end

target/rubygems/gems/activestorage-5.2.3/lib/active_storage/attached.rb

63.16% lines covered

19 relevant lines. 12 lines covered and 7 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "action_dispatch"
  3. 1 require "action_dispatch/http/upload"
  4. 1 require "active_support/core_ext/module/delegation"
  5. 1 module ActiveStorage
  6. # Abstract base class for the concrete ActiveStorage::Attached::One and ActiveStorage::Attached::Many
  7. # classes that both provide proxy access to the blob association for a record.
  8. 1 class Attached
  9. 1 attr_reader :name, :record, :dependent
  10. 1 def initialize(name, record, dependent:)
  11. @name, @record, @dependent = name, record, dependent
  12. end
  13. 1 private
  14. 1 def create_blob_from(attachable)
  15. case attachable
  16. when ActiveStorage::Blob
  17. attachable
  18. when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
  19. ActiveStorage::Blob.create_after_upload! \
  20. io: attachable.open,
  21. filename: attachable.original_filename,
  22. content_type: attachable.content_type
  23. when Hash
  24. ActiveStorage::Blob.create_after_upload!(attachable)
  25. when String
  26. ActiveStorage::Blob.find_signed(attachable)
  27. else
  28. nil
  29. end
  30. end
  31. end
  32. end
  33. 1 require "active_storage/attached/one"
  34. 1 require "active_storage/attached/many"
  35. 1 require "active_storage/attached/macros"

target/rubygems/gems/activestorage-5.2.3/lib/active_storage/attached/macros.rb

12.5% lines covered

32 relevant lines. 4 lines covered and 28 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveStorage
  3. # Provides the class-level DSL for declaring that an Active Record model has attached blobs.
  4. 1 module Attached::Macros
  5. # Specifies the relation between a single attachment and the model.
  6. #
  7. # class User < ActiveRecord::Base
  8. # has_one_attached :avatar
  9. # end
  10. #
  11. # There is no column defined on the model side, Active Storage takes
  12. # care of the mapping between your records and the attachment.
  13. #
  14. # To avoid N+1 queries, you can include the attached blobs in your query like so:
  15. #
  16. # User.with_attached_avatar
  17. #
  18. # Under the covers, this relationship is implemented as a +has_one+ association to a
  19. # ActiveStorage::Attachment record and a +has_one-through+ association to a
  20. # ActiveStorage::Blob record. These associations are available as +avatar_attachment+
  21. # and +avatar_blob+. But you shouldn't need to work with these associations directly in
  22. # most circumstances.
  23. #
  24. # The system has been designed to having you go through the ActiveStorage::Attached::One
  25. # proxy that provides the dynamic proxy to the associations and factory methods, like +attach+.
  26. #
  27. # If the +:dependent+ option isn't set, the attachment will be purged
  28. # (i.e. destroyed) whenever the record is destroyed.
  29. 1 def has_one_attached(name, dependent: :purge_later)
  30. class_eval <<-CODE, __FILE__, __LINE__ + 1
  31. def #{name}
  32. @active_storage_attached_#{name} ||= ActiveStorage::Attached::One.new("#{name}", self, dependent: #{dependent == :purge_later ? ":purge_later" : "false"})
  33. end
  34. def #{name}=(attachable)
  35. #{name}.attach(attachable)
  36. end
  37. CODE
  38. has_one :"#{name}_attachment", -> { where(name: name) }, class_name: "ActiveStorage::Attachment", as: :record, inverse_of: :record, dependent: false
  39. has_one :"#{name}_blob", through: :"#{name}_attachment", class_name: "ActiveStorage::Blob", source: :blob
  40. scope :"with_attached_#{name}", -> { includes("#{name}_attachment": :blob) }
  41. if dependent == :purge_later
  42. after_destroy_commit { public_send(name).purge_later }
  43. else
  44. before_destroy { public_send(name).detach }
  45. end
  46. end
  47. # Specifies the relation between multiple attachments and the model.
  48. #
  49. # class Gallery < ActiveRecord::Base
  50. # has_many_attached :photos
  51. # end
  52. #
  53. # There are no columns defined on the model side, Active Storage takes
  54. # care of the mapping between your records and the attachments.
  55. #
  56. # To avoid N+1 queries, you can include the attached blobs in your query like so:
  57. #
  58. # Gallery.where(user: Current.user).with_attached_photos
  59. #
  60. # Under the covers, this relationship is implemented as a +has_many+ association to a
  61. # ActiveStorage::Attachment record and a +has_many-through+ association to a
  62. # ActiveStorage::Blob record. These associations are available as +photos_attachments+
  63. # and +photos_blobs+. But you shouldn't need to work with these associations directly in
  64. # most circumstances.
  65. #
  66. # The system has been designed to having you go through the ActiveStorage::Attached::Many
  67. # proxy that provides the dynamic proxy to the associations and factory methods, like +#attach+.
  68. #
  69. # If the +:dependent+ option isn't set, all the attachments will be purged
  70. # (i.e. destroyed) whenever the record is destroyed.
  71. 1 def has_many_attached(name, dependent: :purge_later)
  72. class_eval <<-CODE, __FILE__, __LINE__ + 1
  73. def #{name}
  74. @active_storage_attached_#{name} ||= ActiveStorage::Attached::Many.new("#{name}", self, dependent: #{dependent == :purge_later ? ":purge_later" : "false"})
  75. end
  76. def #{name}=(attachables)
  77. #{name}.attach(attachables)
  78. end
  79. CODE
  80. has_many :"#{name}_attachments", -> { where(name: name) }, as: :record, class_name: "ActiveStorage::Attachment", inverse_of: :record, dependent: false do
  81. def purge
  82. each(&:purge)
  83. reset
  84. end
  85. def purge_later
  86. each(&:purge_later)
  87. reset
  88. end
  89. end
  90. has_many :"#{name}_blobs", through: :"#{name}_attachments", class_name: "ActiveStorage::Blob", source: :blob
  91. scope :"with_attached_#{name}", -> { includes("#{name}_attachments": :blob) }
  92. if dependent == :purge_later
  93. after_destroy_commit { public_send(name).purge_later }
  94. else
  95. before_destroy { public_send(name).detach }
  96. end
  97. end
  98. end
  99. end

target/rubygems/gems/activestorage-5.2.3/lib/active_storage/attached/many.rb

50.0% lines covered

14 relevant lines. 7 lines covered and 7 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveStorage
  3. # Decorated proxy object representing of multiple attachments to a model.
  4. 1 class Attached::Many < Attached
  5. 1 delegate_missing_to :attachments
  6. # Returns all the associated attachment records.
  7. #
  8. # All methods called on this proxy object that aren't listed here will automatically be delegated to +attachments+.
  9. 1 def attachments
  10. record.public_send("#{name}_attachments")
  11. end
  12. # Associates one or several attachments with the current record, saving them to the database.
  13. #
  14. # document.images.attach(params[:images]) # Array of ActionDispatch::Http::UploadedFile objects
  15. # document.images.attach(params[:signed_blob_id]) # Signed reference to blob from direct upload
  16. # document.images.attach(io: File.open("/path/to/racecar.jpg"), filename: "racecar.jpg", content_type: "image/jpg")
  17. # document.images.attach([ first_blob, second_blob ])
  18. 1 def attach(*attachables)
  19. attachables.flatten.collect do |attachable|
  20. if record.new_record?
  21. attachments.build(record: record, blob: create_blob_from(attachable))
  22. else
  23. attachments.create!(record: record, blob: create_blob_from(attachable))
  24. end
  25. end
  26. end
  27. # Returns true if any attachments has been made.
  28. #
  29. # class Gallery < ActiveRecord::Base
  30. # has_many_attached :photos
  31. # end
  32. #
  33. # Gallery.new.photos.attached? # => false
  34. 1 def attached?
  35. attachments.any?
  36. end
  37. # Deletes associated attachments without purging them, leaving their respective blobs in place.
  38. 1 def detach
  39. attachments.destroy_all if attached?
  40. end
  41. ##
  42. # :method: purge
  43. #
  44. # Directly purges each associated attachment (i.e. destroys the blobs and
  45. # attachments and deletes the files on the service).
  46. ##
  47. # :method: purge_later
  48. #
  49. # Purges each associated attachment through the queuing system.
  50. end
  51. end

target/rubygems/gems/activestorage-5.2.3/lib/active_storage/attached/one.rb

40.63% lines covered

32 relevant lines. 13 lines covered and 19 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveStorage
  3. # Representation of a single attachment to a model.
  4. 1 class Attached::One < Attached
  5. 1 delegate_missing_to :attachment
  6. # Returns the associated attachment record.
  7. #
  8. # You don't have to call this method to access the attachment's methods as
  9. # they are all available at the model level.
  10. 1 def attachment
  11. record.public_send("#{name}_attachment")
  12. end
  13. # Associates a given attachment with the current record, saving it to the database.
  14. #
  15. # person.avatar.attach(params[:avatar]) # ActionDispatch::Http::UploadedFile object
  16. # person.avatar.attach(params[:signed_blob_id]) # Signed reference to blob from direct upload
  17. # person.avatar.attach(io: File.open("/path/to/face.jpg"), filename: "face.jpg", content_type: "image/jpg")
  18. # person.avatar.attach(avatar_blob) # ActiveStorage::Blob object
  19. 1 def attach(attachable)
  20. blob_was = blob if attached?
  21. blob = create_blob_from(attachable)
  22. unless blob == blob_was
  23. transaction do
  24. detach
  25. write_attachment build_attachment(blob: blob)
  26. end
  27. blob_was.purge_later if blob_was && dependent == :purge_later
  28. end
  29. end
  30. # Returns +true+ if an attachment has been made.
  31. #
  32. # class User < ActiveRecord::Base
  33. # has_one_attached :avatar
  34. # end
  35. #
  36. # User.new.avatar.attached? # => false
  37. 1 def attached?
  38. attachment.present?
  39. end
  40. # Deletes the attachment without purging it, leaving its blob in place.
  41. 1 def detach
  42. if attached?
  43. attachment.destroy
  44. write_attachment nil
  45. end
  46. end
  47. # Directly purges the attachment (i.e. destroys the blob and
  48. # attachment and deletes the file on the service).
  49. 1 def purge
  50. if attached?
  51. attachment.purge
  52. write_attachment nil
  53. end
  54. end
  55. # Purges the attachment through the queuing system.
  56. 1 def purge_later
  57. if attached?
  58. attachment.purge_later
  59. end
  60. end
  61. 1 private
  62. 1 delegate :transaction, to: :record
  63. 1 def build_attachment(blob:)
  64. ActiveStorage::Attachment.new(record: record, name: name, blob: blob)
  65. end
  66. 1 def write_attachment(attachment)
  67. record.public_send("#{name}_attachment=", attachment)
  68. end
  69. end
  70. end

target/rubygems/gems/activesupport-5.2.3/lib/active_support/all.rb

100.0% lines covered

3 relevant lines. 3 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support"
  3. 1 require "active_support/time"
  4. 1 require "active_support/core_ext"

target/rubygems/gems/activesupport-5.2.3/lib/active_support/backtrace_cleaner.rb

53.33% lines covered

30 relevant lines. 16 lines covered and 14 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveSupport
  3. # Backtraces often include many lines that are not relevant for the context
  4. # under review. This makes it hard to find the signal amongst the backtrace
  5. # noise, and adds debugging time. With a BacktraceCleaner, filters and
  6. # silencers are used to remove the noisy lines, so that only the most relevant
  7. # lines remain.
  8. #
  9. # Filters are used to modify lines of data, while silencers are used to remove
  10. # lines entirely. The typical filter use case is to remove lengthy path
  11. # information from the start of each line, and view file paths relevant to the
  12. # app directory instead of the file system root. The typical silencer use case
  13. # is to exclude the output of a noisy library from the backtrace, so that you
  14. # can focus on the rest.
  15. #
  16. # bc = ActiveSupport::BacktraceCleaner.new
  17. # bc.add_filter { |line| line.gsub(Rails.root.to_s, '') } # strip the Rails.root prefix
  18. # bc.add_silencer { |line| line =~ /puma|rubygems/ } # skip any lines from puma or rubygems
  19. # bc.clean(exception.backtrace) # perform the cleanup
  20. #
  21. # To reconfigure an existing BacktraceCleaner (like the default one in Rails)
  22. # and show as much data as possible, you can always call
  23. # <tt>BacktraceCleaner#remove_silencers!</tt>, which will restore the
  24. # backtrace to a pristine state. If you need to reconfigure an existing
  25. # BacktraceCleaner so that it does not filter or modify the paths of any lines
  26. # of the backtrace, you can call <tt>BacktraceCleaner#remove_filters!</tt>
  27. # These two methods will give you a completely untouched backtrace.
  28. #
  29. # Inspired by the Quiet Backtrace gem by thoughtbot.
  30. 1 class BacktraceCleaner
  31. 1 def initialize
  32. 1 @filters, @silencers = [], []
  33. end
  34. # Returns the backtrace after all filters and silencers have been run
  35. # against it. Filters run first, then silencers.
  36. 1 def clean(backtrace, kind = :silent)
  37. filtered = filter_backtrace(backtrace)
  38. case kind
  39. when :silent
  40. silence(filtered)
  41. when :noise
  42. noise(filtered)
  43. else
  44. filtered
  45. end
  46. end
  47. 1 alias :filter :clean
  48. # Adds a filter from the block provided. Each line in the backtrace will be
  49. # mapped against this filter.
  50. #
  51. # # Will turn "/my/rails/root/app/models/person.rb" into "/app/models/person.rb"
  52. # backtrace_cleaner.add_filter { |line| line.gsub(Rails.root, '') }
  53. 1 def add_filter(&block)
  54. 4 @filters << block
  55. end
  56. # Adds a silencer from the block provided. If the silencer returns +true+
  57. # for a given line, it will be excluded from the clean backtrace.
  58. #
  59. # # Will reject all lines that include the word "puma", like "/gems/puma/server.rb" or "/app/my_puma_server/rb"
  60. # backtrace_cleaner.add_silencer { |line| line =~ /puma/ }
  61. 1 def add_silencer(&block)
  62. 1 @silencers << block
  63. end
  64. # Removes all silencers, but leaves in the filters. Useful if your
  65. # context of debugging suddenly expands as you suspect a bug in one of
  66. # the libraries you use.
  67. 1 def remove_silencers!
  68. @silencers = []
  69. end
  70. # Removes all filters, but leaves in the silencers. Useful if you suddenly
  71. # need to see entire filepaths in the backtrace that you had already
  72. # filtered out.
  73. 1 def remove_filters!
  74. @filters = []
  75. end
  76. 1 private
  77. 1 def filter_backtrace(backtrace)
  78. @filters.each do |f|
  79. backtrace = backtrace.map { |line| f.call(line) }
  80. end
  81. backtrace
  82. end
  83. 1 def silence(backtrace)
  84. @silencers.each do |s|
  85. backtrace = backtrace.reject { |line| s.call(line) }
  86. end
  87. backtrace
  88. end
  89. 1 def noise(backtrace)
  90. backtrace - silence(backtrace)
  91. end
  92. end
  93. end

target/rubygems/gems/activesupport-5.2.3/lib/active_support/cache/memory_store.rb

32.97% lines covered

91 relevant lines. 30 lines covered and 61 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "monitor"
  3. 1 module ActiveSupport
  4. 1 module Cache
  5. # A cache store implementation which stores everything into memory in the
  6. # same process. If you're running multiple Ruby on Rails server processes
  7. # (which is the case if you're using Phusion Passenger or puma clustered mode),
  8. # then this means that Rails server process instances won't be able
  9. # to share cache data with each other and this may not be the most
  10. # appropriate cache in that scenario.
  11. #
  12. # This cache has a bounded size specified by the :size options to the
  13. # initializer (default is 32Mb). When the cache exceeds the allotted size,
  14. # a cleanup will occur which tries to prune the cache down to three quarters
  15. # of the maximum size by removing the least recently used entries.
  16. #
  17. # MemoryStore is thread-safe.
  18. 1 class MemoryStore < Store
  19. 1 def initialize(options = nil)
  20. 1 options ||= {}
  21. 1 super(options)
  22. 1 @data = {}
  23. 1 @key_access = {}
  24. 1 @max_size = options[:size] || 32.megabytes
  25. 1 @max_prune_time = options[:max_prune_time] || 2
  26. 1 @cache_size = 0
  27. 1 @monitor = Monitor.new
  28. 1 @pruning = false
  29. end
  30. # Delete all data stored in a given cache store.
  31. 1 def clear(options = nil)
  32. synchronize do
  33. @data.clear
  34. @key_access.clear
  35. @cache_size = 0
  36. end
  37. end
  38. # Preemptively iterates through all stored keys and removes the ones which have expired.
  39. 1 def cleanup(options = nil)
  40. options = merged_options(options)
  41. instrument(:cleanup, size: @data.size) do
  42. keys = synchronize { @data.keys }
  43. keys.each do |key|
  44. entry = @data[key]
  45. delete_entry(key, options) if entry && entry.expired?
  46. end
  47. end
  48. end
  49. # To ensure entries fit within the specified memory prune the cache by removing the least
  50. # recently accessed entries.
  51. 1 def prune(target_size, max_time = nil)
  52. return if pruning?
  53. @pruning = true
  54. begin
  55. start_time = Time.now
  56. cleanup
  57. instrument(:prune, target_size, from: @cache_size) do
  58. keys = synchronize { @key_access.keys.sort { |a, b| @key_access[a].to_f <=> @key_access[b].to_f } }
  59. keys.each do |key|
  60. delete_entry(key, options)
  61. return if @cache_size <= target_size || (max_time && Time.now - start_time > max_time)
  62. end
  63. end
  64. ensure
  65. @pruning = false
  66. end
  67. end
  68. # Returns true if the cache is currently being pruned.
  69. 1 def pruning?
  70. @pruning
  71. end
  72. # Increment an integer value in the cache.
  73. 1 def increment(name, amount = 1, options = nil)
  74. modify_value(name, amount, options)
  75. end
  76. # Decrement an integer value in the cache.
  77. 1 def decrement(name, amount = 1, options = nil)
  78. modify_value(name, -amount, options)
  79. end
  80. # Deletes cache entries if the cache key matches a given pattern.
  81. 1 def delete_matched(matcher, options = nil)
  82. options = merged_options(options)
  83. instrument(:delete_matched, matcher.inspect) do
  84. matcher = key_matcher(matcher, options)
  85. keys = synchronize { @data.keys }
  86. keys.each do |key|
  87. delete_entry(key, options) if key.match(matcher)
  88. end
  89. end
  90. end
  91. 1 def inspect # :nodoc:
  92. "<##{self.class.name} entries=#{@data.size}, size=#{@cache_size}, options=#{@options.inspect}>"
  93. end
  94. # Synchronize calls to the cache. This should be called wherever the underlying cache implementation
  95. # is not thread safe.
  96. 1 def synchronize(&block) # :nodoc:
  97. @monitor.synchronize(&block)
  98. end
  99. 1 private
  100. 1 PER_ENTRY_OVERHEAD = 240
  101. 1 def cached_size(key, entry)
  102. key.to_s.bytesize + entry.size + PER_ENTRY_OVERHEAD
  103. end
  104. 1 def read_entry(key, options)
  105. entry = @data[key]
  106. synchronize do
  107. if entry
  108. @key_access[key] = Time.now.to_f
  109. else
  110. @key_access.delete(key)
  111. end
  112. end
  113. entry
  114. end
  115. 1 def write_entry(key, entry, options)
  116. entry.dup_value!
  117. synchronize do
  118. old_entry = @data[key]
  119. return false if @data.key?(key) && options[:unless_exist]
  120. if old_entry
  121. @cache_size -= (old_entry.size - entry.size)
  122. else
  123. @cache_size += cached_size(key, entry)
  124. end
  125. @key_access[key] = Time.now.to_f
  126. @data[key] = entry
  127. prune(@max_size * 0.75, @max_prune_time) if @cache_size > @max_size
  128. true
  129. end
  130. end
  131. 1 def delete_entry(key, options)
  132. synchronize do
  133. @key_access.delete(key)
  134. entry = @data.delete(key)
  135. @cache_size -= cached_size(key, entry) if entry
  136. !!entry
  137. end
  138. end
  139. 1 def modify_value(name, amount, options)
  140. synchronize do
  141. options = merged_options(options)
  142. if num = read(name, options)
  143. num = num.to_i + amount
  144. write(name, num, options)
  145. num
  146. end
  147. end
  148. end
  149. end
  150. end
  151. end

target/rubygems/gems/activesupport-5.2.3/lib/active_support/cache/null_store.rb

86.67% lines covered

15 relevant lines. 13 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveSupport
  3. 1 module Cache
  4. # A cache store implementation which doesn't actually store anything. Useful in
  5. # development and test environments where you don't want caching turned on but
  6. # need to go through the caching interface.
  7. #
  8. # This cache does implement the local cache strategy, so values will actually
  9. # be cached inside blocks that utilize this strategy. See
  10. # ActiveSupport::Cache::Strategy::LocalCache for more details.
  11. 1 class NullStore < Store
  12. 1 prepend Strategy::LocalCache
  13. 1 def clear(options = nil)
  14. end
  15. 1 def cleanup(options = nil)
  16. end
  17. 1 def increment(name, amount = 1, options = nil)
  18. end
  19. 1 def decrement(name, amount = 1, options = nil)
  20. end
  21. 1 def delete_matched(matcher, options = nil)
  22. end
  23. 1 private
  24. 1 def read_entry(key, options)
  25. end
  26. 1 def write_entry(key, entry, options)
  27. true
  28. end
  29. 1 def delete_entry(key, options)
  30. false
  31. end
  32. end
  33. end
  34. end

target/rubygems/gems/activesupport-5.2.3/lib/active_support/cache/strategy/local_cache.rb

44.66% lines covered

103 relevant lines. 46 lines covered and 57 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/object/duplicable"
  3. 1 require "active_support/core_ext/string/inflections"
  4. 1 require "active_support/per_thread_registry"
  5. 1 module ActiveSupport
  6. 1 module Cache
  7. 1 module Strategy
  8. # Caches that implement LocalCache will be backed by an in-memory cache for the
  9. # duration of a block. Repeated calls to the cache for the same key will hit the
  10. # in-memory cache for faster access.
  11. 1 module LocalCache
  12. 1 autoload :Middleware, "active_support/cache/strategy/local_cache_middleware"
  13. # Class for storing and registering the local caches.
  14. 1 class LocalCacheRegistry # :nodoc:
  15. 1 extend ActiveSupport::PerThreadRegistry
  16. 1 def initialize
  17. 1 @registry = {}
  18. end
  19. 1 def cache_for(local_cache_key)
  20. @registry[local_cache_key]
  21. end
  22. 1 def set_cache_for(local_cache_key, value)
  23. 4 @registry[local_cache_key] = value
  24. end
  25. 5 def self.set_cache_for(l, v); instance.set_cache_for l, v; end
  26. 1 def self.cache_for(l); instance.cache_for l; end
  27. end
  28. # Simple memory backed cache. This cache is not thread safe and is intended only
  29. # for serving as a temporary memory cache for a single thread.
  30. 1 class LocalStore < Store
  31. 1 def initialize
  32. 2 super
  33. 2 @data = {}
  34. end
  35. # Don't allow synchronizing since it isn't thread safe.
  36. 1 def synchronize # :nodoc:
  37. yield
  38. end
  39. 1 def clear(options = nil)
  40. @data.clear
  41. end
  42. 1 def read_entry(key, options)
  43. @data[key]
  44. end
  45. 1 def read_multi_entries(keys, options)
  46. values = {}
  47. keys.each do |name|
  48. entry = read_entry(name, options)
  49. values[name] = entry.value if entry
  50. end
  51. values
  52. end
  53. 1 def write_entry(key, value, options)
  54. @data[key] = value
  55. true
  56. end
  57. 1 def delete_entry(key, options)
  58. !!@data.delete(key)
  59. end
  60. 1 def fetch_entry(key, options = nil) # :nodoc:
  61. @data.fetch(key) { @data[key] = yield }
  62. end
  63. end
  64. # Use a local cache for the duration of block.
  65. 1 def with_local_cache
  66. use_temporary_local_cache(LocalStore.new) { yield }
  67. end
  68. # Middleware class can be inserted as a Rack handler to be local cache for the
  69. # duration of request.
  70. 1 def middleware
  71. 2 @middleware ||= Middleware.new(
  72. "ActiveSupport::Cache::Strategy::LocalCache",
  73. local_cache_key)
  74. end
  75. 1 def clear(options = nil) # :nodoc:
  76. return super unless cache = local_cache
  77. cache.clear(options)
  78. super
  79. end
  80. 1 def cleanup(options = nil) # :nodoc:
  81. return super unless cache = local_cache
  82. cache.clear
  83. super
  84. end
  85. 1 def increment(name, amount = 1, options = nil) # :nodoc:
  86. return super unless local_cache
  87. value = bypass_local_cache { super }
  88. write_cache_value(name, value, options)
  89. value
  90. end
  91. 1 def decrement(name, amount = 1, options = nil) # :nodoc:
  92. return super unless local_cache
  93. value = bypass_local_cache { super }
  94. write_cache_value(name, value, options)
  95. value
  96. end
  97. 1 private
  98. 1 def read_entry(key, options)
  99. if cache = local_cache
  100. cache.fetch_entry(key) { super }
  101. else
  102. super
  103. end
  104. end
  105. 1 def read_multi_entries(keys, options)
  106. return super unless local_cache
  107. local_entries = local_cache.read_multi_entries(keys, options)
  108. missed_keys = keys - local_entries.keys
  109. if missed_keys.any?
  110. local_entries.merge!(super(missed_keys, options))
  111. else
  112. local_entries
  113. end
  114. end
  115. 1 def write_entry(key, entry, options)
  116. if options[:unless_exist]
  117. local_cache.delete_entry(key, options) if local_cache
  118. else
  119. local_cache.write_entry(key, entry, options) if local_cache
  120. end
  121. super
  122. end
  123. 1 def delete_entry(key, options)
  124. local_cache.delete_entry(key, options) if local_cache
  125. super
  126. end
  127. 1 def write_cache_value(name, value, options)
  128. name = normalize_key(name, options)
  129. cache = local_cache
  130. cache.mute do
  131. if value
  132. cache.write(name, value, options)
  133. else
  134. cache.delete(name, options)
  135. end
  136. end
  137. end
  138. 1 def local_cache_key
  139. 1 @local_cache_key ||= "#{self.class.name.underscore}_local_cache_#{object_id}".gsub(/[\/-]/, "_").to_sym
  140. end
  141. 1 def local_cache
  142. LocalCacheRegistry.cache_for(local_cache_key)
  143. end
  144. 1 def bypass_local_cache
  145. use_temporary_local_cache(nil) { yield }
  146. end
  147. 1 def use_temporary_local_cache(temporary_cache)
  148. save_cache = LocalCacheRegistry.cache_for(local_cache_key)
  149. begin
  150. LocalCacheRegistry.set_cache_for(local_cache_key, temporary_cache)
  151. yield
  152. ensure
  153. LocalCacheRegistry.set_cache_for(local_cache_key, save_cache)
  154. end
  155. end
  156. end
  157. end
  158. end
  159. end

target/rubygems/gems/activesupport-5.2.3/lib/active_support/cache/strategy/local_cache_middleware.rb

95.83% lines covered

24 relevant lines. 23 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "rack/body_proxy"
  3. 1 require "rack/utils"
  4. 1 module ActiveSupport
  5. 1 module Cache
  6. 1 module Strategy
  7. 1 module LocalCache
  8. #--
  9. # This class wraps up local storage for middlewares. Only the middleware method should
  10. # construct them.
  11. 1 class Middleware # :nodoc:
  12. 1 attr_reader :name, :local_cache_key
  13. 1 def initialize(name, local_cache_key)
  14. 1 @name = name
  15. 1 @local_cache_key = local_cache_key
  16. 1 @app = nil
  17. end
  18. 1 def new(app)
  19. 1 @app = app
  20. 1 self
  21. end
  22. 1 def call(env)
  23. 2 LocalCacheRegistry.set_cache_for(local_cache_key, LocalStore.new)
  24. 2 response = @app.call(env)
  25. 2 response[2] = ::Rack::BodyProxy.new(response[2]) do
  26. 2 LocalCacheRegistry.set_cache_for(local_cache_key, nil)
  27. end
  28. 2 cleanup_on_body_close = true
  29. 2 response
  30. rescue Rack::Utils::InvalidParameterError
  31. [400, {}, []]
  32. ensure
  33. LocalCacheRegistry.set_cache_for(local_cache_key, nil) unless
  34. 2 cleanup_on_body_close
  35. end
  36. end
  37. end
  38. end
  39. end
  40. end

target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext.rb

100.0% lines covered

2 relevant lines. 2 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 Dir.glob(File.expand_path("core_ext/*.rb", __dir__)).each do |path|
  3. 23 require path
  4. end

target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/big_decimal.rb

100.0% lines covered

1 relevant lines. 1 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/big_decimal/conversions"

target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/digest/uuid.rb

44.0% lines covered

25 relevant lines. 11 lines covered and 14 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "securerandom"
  3. 1 module Digest
  4. 1 module UUID
  5. 1 DNS_NAMESPACE = "k\xA7\xB8\x10\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
  6. 1 URL_NAMESPACE = "k\xA7\xB8\x11\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
  7. 1 OID_NAMESPACE = "k\xA7\xB8\x12\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
  8. 1 X500_NAMESPACE = "k\xA7\xB8\x14\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
  9. # Generates a v5 non-random UUID (Universally Unique IDentifier).
  10. #
  11. # Using Digest::MD5 generates version 3 UUIDs; Digest::SHA1 generates version 5 UUIDs.
  12. # uuid_from_hash always generates the same UUID for a given name and namespace combination.
  13. #
  14. # See RFC 4122 for details of UUID at: https://www.ietf.org/rfc/rfc4122.txt
  15. 1 def self.uuid_from_hash(hash_class, uuid_namespace, name)
  16. if hash_class == Digest::MD5
  17. version = 3
  18. elsif hash_class == Digest::SHA1
  19. version = 5
  20. else
  21. raise ArgumentError, "Expected Digest::SHA1 or Digest::MD5, got #{hash_class.name}."
  22. end
  23. hash = hash_class.new
  24. hash.update(uuid_namespace)
  25. hash.update(name)
  26. ary = hash.digest.unpack("NnnnnN")
  27. ary[2] = (ary[2] & 0x0FFF) | (version << 12)
  28. ary[3] = (ary[3] & 0x3FFF) | 0x8000
  29. "%08x-%04x-%04x-%04x-%04x%08x" % ary
  30. end
  31. # Convenience method for uuid_from_hash using Digest::MD5.
  32. 1 def self.uuid_v3(uuid_namespace, name)
  33. uuid_from_hash(Digest::MD5, uuid_namespace, name)
  34. end
  35. # Convenience method for uuid_from_hash using Digest::SHA1.
  36. 1 def self.uuid_v5(uuid_namespace, name)
  37. uuid_from_hash(Digest::SHA1, uuid_namespace, name)
  38. end
  39. # Convenience method for SecureRandom.uuid.
  40. 1 def self.uuid_v4
  41. SecureRandom.uuid
  42. end
  43. end
  44. end

target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/file.rb

100.0% lines covered

1 relevant lines. 1 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/file/atomic"

target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/file/atomic.rb

17.39% lines covered

23 relevant lines. 4 lines covered and 19 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "fileutils"
  3. 1 class File
  4. # Write to a file atomically. Useful for situations where you don't
  5. # want other processes or threads to see half-written files.
  6. #
  7. # File.atomic_write('important.file') do |file|
  8. # file.write('hello')
  9. # end
  10. #
  11. # This method needs to create a temporary file. By default it will create it
  12. # in the same directory as the destination file. If you don't like this
  13. # behavior you can provide a different directory but it must be on the
  14. # same physical filesystem as the file you're trying to write.
  15. #
  16. # File.atomic_write('/data/something.important', '/data/tmp') do |file|
  17. # file.write('hello')
  18. # end
  19. 1 def self.atomic_write(file_name, temp_dir = dirname(file_name))
  20. require "tempfile" unless defined?(Tempfile)
  21. Tempfile.open(".#{basename(file_name)}", temp_dir) do |temp_file|
  22. temp_file.binmode
  23. return_val = yield temp_file
  24. temp_file.close
  25. old_stat = if exist?(file_name)
  26. # Get original file permissions
  27. stat(file_name)
  28. else
  29. # If not possible, probe which are the default permissions in the
  30. # destination directory.
  31. probe_stat_in(dirname(file_name))
  32. end
  33. if old_stat
  34. # Set correct permissions on new file
  35. begin
  36. chown(old_stat.uid, old_stat.gid, temp_file.path)
  37. # This operation will affect filesystem ACL's
  38. chmod(old_stat.mode, temp_file.path)
  39. rescue Errno::EPERM, Errno::EACCES
  40. # Changing file ownership failed, moving on.
  41. end
  42. end
  43. # Overwrite original file with temp file
  44. rename(temp_file.path, file_name)
  45. return_val
  46. end
  47. end
  48. # Private utility method.
  49. 1 def self.probe_stat_in(dir) #:nodoc:
  50. basename = [
  51. ".permissions_check",
  52. Thread.current.object_id,
  53. Process.pid,
  54. rand(1000000)
  55. ].join(".")
  56. file_name = join(dir, basename)
  57. FileUtils.touch(file_name)
  58. stat(file_name)
  59. ensure
  60. FileUtils.rm_f(file_name) if file_name
  61. end
  62. end

target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/hash.rb

100.0% lines covered

9 relevant lines. 9 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/hash/compact"
  3. 1 require "active_support/core_ext/hash/conversions"
  4. 1 require "active_support/core_ext/hash/deep_merge"
  5. 1 require "active_support/core_ext/hash/except"
  6. 1 require "active_support/core_ext/hash/indifferent_access"
  7. 1 require "active_support/core_ext/hash/keys"
  8. 1 require "active_support/core_ext/hash/reverse_merge"
  9. 1 require "active_support/core_ext/hash/slice"
  10. 1 require "active_support/core_ext/hash/transform_values"

target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/integer.rb

100.0% lines covered

3 relevant lines. 3 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/integer/multiple"
  3. 1 require "active_support/core_ext/integer/inflections"
  4. 1 require "active_support/core_ext/integer/time"

target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/integer/inflections.rb

66.67% lines covered

6 relevant lines. 4 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/inflector"
  3. 1 class Integer
  4. # Ordinalize turns a number into an ordinal string used to denote the
  5. # position in an ordered sequence such as 1st, 2nd, 3rd, 4th.
  6. #
  7. # 1.ordinalize # => "1st"
  8. # 2.ordinalize # => "2nd"
  9. # 1002.ordinalize # => "1002nd"
  10. # 1003.ordinalize # => "1003rd"
  11. # -11.ordinalize # => "-11th"
  12. # -1001.ordinalize # => "-1001st"
  13. 1 def ordinalize
  14. ActiveSupport::Inflector.ordinalize(self)
  15. end
  16. # Ordinal returns the suffix used to denote the position
  17. # in an ordered sequence such as 1st, 2nd, 3rd, 4th.
  18. #
  19. # 1.ordinal # => "st"
  20. # 2.ordinal # => "nd"
  21. # 1002.ordinal # => "nd"
  22. # 1003.ordinal # => "rd"
  23. # -11.ordinal # => "th"
  24. # -1001.ordinal # => "st"
  25. 1 def ordinal
  26. ActiveSupport::Inflector.ordinal(self)
  27. end
  28. end

target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/integer/multiple.rb

66.67% lines covered

3 relevant lines. 2 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class Integer
  3. # Check whether the integer is evenly divisible by the argument.
  4. #
  5. # 0.multiple_of?(0) # => true
  6. # 6.multiple_of?(5) # => false
  7. # 10.multiple_of?(2) # => true
  8. 1 def multiple_of?(number)
  9. number != 0 ? self % number == 0 : zero?
  10. end
  11. end

target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/kernel.rb

100.0% lines covered

4 relevant lines. 4 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/kernel/agnostics"
  3. 1 require "active_support/core_ext/kernel/concern"
  4. 1 require "active_support/core_ext/kernel/reporting"
  5. 1 require "active_support/core_ext/kernel/singleton_class"

target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/kernel/agnostics.rb

50.0% lines covered

4 relevant lines. 2 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class Object
  3. # Makes backticks behave (somewhat more) similarly on all platforms.
  4. # On win32 `nonexistent_command` raises Errno::ENOENT; on Unix, the
  5. # spawned shell prints a message to stderr and sets $?. We emulate
  6. # Unix on the former but not the latter.
  7. 1 def `(command) #:nodoc:
  8. super
  9. rescue Errno::ENOENT => e
  10. STDERR.puts "#$0: #{e}"
  11. end
  12. end

target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/kernel/concern.rb

80.0% lines covered

5 relevant lines. 4 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/module/concerning"
  3. 1 module Kernel
  4. 1 module_function
  5. # A shortcut to define a toplevel concern, not within a module.
  6. #
  7. # See Module::Concerning for more.
  8. 1 def concern(topic, &module_definition)
  9. Object.concern topic, &module_definition
  10. end
  11. end

target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/marshal.rb

45.45% lines covered

11 relevant lines. 5 lines covered and 6 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveSupport
  3. 1 module MarshalWithAutoloading # :nodoc:
  4. 1 def load(source, proc = nil)
  5. 1 super(source, proc)
  6. rescue ArgumentError, NameError => exc
  7. if exc.message.match(%r|undefined class/module (.+?)(?:::)?\z|)
  8. # try loading the class/module
  9. loaded = $1.constantize
  10. raise unless $1 == loaded.name
  11. # if it is an IO we need to go back to read the object
  12. source.rewind if source.respond_to?(:rewind)
  13. retry
  14. else
  15. raise exc
  16. end
  17. end
  18. end
  19. end
  20. 1 Marshal.singleton_class.prepend(ActiveSupport::MarshalWithAutoloading)

target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/numeric.rb

100.0% lines covered

4 relevant lines. 4 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/numeric/bytes"
  3. 1 require "active_support/core_ext/numeric/time"
  4. 1 require "active_support/core_ext/numeric/inquiry"
  5. 1 require "active_support/core_ext/numeric/conversions"

target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/numeric/conversions.rb

47.83% lines covered

23 relevant lines. 11 lines covered and 12 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/big_decimal/conversions"
  3. 1 require "active_support/number_helper"
  4. 1 require "active_support/core_ext/module/deprecation"
  5. 1 module ActiveSupport::NumericWithFormat
  6. # Provides options for converting numbers into formatted strings.
  7. # Options are provided for phone numbers, currency, percentage,
  8. # precision, positional notation, file size and pretty printing.
  9. #
  10. # ==== Options
  11. #
  12. # For details on which formats use which options, see ActiveSupport::NumberHelper
  13. #
  14. # ==== Examples
  15. #
  16. # Phone Numbers:
  17. # 5551234.to_s(:phone) # => "555-1234"
  18. # 1235551234.to_s(:phone) # => "123-555-1234"
  19. # 1235551234.to_s(:phone, area_code: true) # => "(123) 555-1234"
  20. # 1235551234.to_s(:phone, delimiter: ' ') # => "123 555 1234"
  21. # 1235551234.to_s(:phone, area_code: true, extension: 555) # => "(123) 555-1234 x 555"
  22. # 1235551234.to_s(:phone, country_code: 1) # => "+1-123-555-1234"
  23. # 1235551234.to_s(:phone, country_code: 1, extension: 1343, delimiter: '.')
  24. # # => "+1.123.555.1234 x 1343"
  25. #
  26. # Currency:
  27. # 1234567890.50.to_s(:currency) # => "$1,234,567,890.50"
  28. # 1234567890.506.to_s(:currency) # => "$1,234,567,890.51"
  29. # 1234567890.506.to_s(:currency, precision: 3) # => "$1,234,567,890.506"
  30. # 1234567890.506.to_s(:currency, locale: :fr) # => "1 234 567 890,51 ���"
  31. # -1234567890.50.to_s(:currency, negative_format: '(%u%n)')
  32. # # => "($1,234,567,890.50)"
  33. # 1234567890.50.to_s(:currency, unit: '&pound;', separator: ',', delimiter: '')
  34. # # => "&pound;1234567890,50"
  35. # 1234567890.50.to_s(:currency, unit: '&pound;', separator: ',', delimiter: '', format: '%n %u')
  36. # # => "1234567890,50 &pound;"
  37. #
  38. # Percentage:
  39. # 100.to_s(:percentage) # => "100.000%"
  40. # 100.to_s(:percentage, precision: 0) # => "100%"
  41. # 1000.to_s(:percentage, delimiter: '.', separator: ',') # => "1.000,000%"
  42. # 302.24398923423.to_s(:percentage, precision: 5) # => "302.24399%"
  43. # 1000.to_s(:percentage, locale: :fr) # => "1 000,000%"
  44. # 100.to_s(:percentage, format: '%n %') # => "100.000 %"
  45. #
  46. # Delimited:
  47. # 12345678.to_s(:delimited) # => "12,345,678"
  48. # 12345678.05.to_s(:delimited) # => "12,345,678.05"
  49. # 12345678.to_s(:delimited, delimiter: '.') # => "12.345.678"
  50. # 12345678.to_s(:delimited, delimiter: ',') # => "12,345,678"
  51. # 12345678.05.to_s(:delimited, separator: ' ') # => "12,345,678 05"
  52. # 12345678.05.to_s(:delimited, locale: :fr) # => "12 345 678,05"
  53. # 98765432.98.to_s(:delimited, delimiter: ' ', separator: ',')
  54. # # => "98 765 432,98"
  55. #
  56. # Rounded:
  57. # 111.2345.to_s(:rounded) # => "111.235"
  58. # 111.2345.to_s(:rounded, precision: 2) # => "111.23"
  59. # 13.to_s(:rounded, precision: 5) # => "13.00000"
  60. # 389.32314.to_s(:rounded, precision: 0) # => "389"
  61. # 111.2345.to_s(:rounded, significant: true) # => "111"
  62. # 111.2345.to_s(:rounded, precision: 1, significant: true) # => "100"
  63. # 13.to_s(:rounded, precision: 5, significant: true) # => "13.000"
  64. # 111.234.to_s(:rounded, locale: :fr) # => "111,234"
  65. # 13.to_s(:rounded, precision: 5, significant: true, strip_insignificant_zeros: true)
  66. # # => "13"
  67. # 389.32314.to_s(:rounded, precision: 4, significant: true) # => "389.3"
  68. # 1111.2345.to_s(:rounded, precision: 2, separator: ',', delimiter: '.')
  69. # # => "1.111,23"
  70. #
  71. # Human-friendly size in Bytes:
  72. # 123.to_s(:human_size) # => "123 Bytes"
  73. # 1234.to_s(:human_size) # => "1.21 KB"
  74. # 12345.to_s(:human_size) # => "12.1 KB"
  75. # 1234567.to_s(:human_size) # => "1.18 MB"
  76. # 1234567890.to_s(:human_size) # => "1.15 GB"
  77. # 1234567890123.to_s(:human_size) # => "1.12 TB"
  78. # 1234567890123456.to_s(:human_size) # => "1.1 PB"
  79. # 1234567890123456789.to_s(:human_size) # => "1.07 EB"
  80. # 1234567.to_s(:human_size, precision: 2) # => "1.2 MB"
  81. # 483989.to_s(:human_size, precision: 2) # => "470 KB"
  82. # 1234567.to_s(:human_size, precision: 2, separator: ',') # => "1,2 MB"
  83. # 1234567890123.to_s(:human_size, precision: 5) # => "1.1228 TB"
  84. # 524288000.to_s(:human_size, precision: 5) # => "500 MB"
  85. #
  86. # Human-friendly format:
  87. # 123.to_s(:human) # => "123"
  88. # 1234.to_s(:human) # => "1.23 Thousand"
  89. # 12345.to_s(:human) # => "12.3 Thousand"
  90. # 1234567.to_s(:human) # => "1.23 Million"
  91. # 1234567890.to_s(:human) # => "1.23 Billion"
  92. # 1234567890123.to_s(:human) # => "1.23 Trillion"
  93. # 1234567890123456.to_s(:human) # => "1.23 Quadrillion"
  94. # 1234567890123456789.to_s(:human) # => "1230 Quadrillion"
  95. # 489939.to_s(:human, precision: 2) # => "490 Thousand"
  96. # 489939.to_s(:human, precision: 4) # => "489.9 Thousand"
  97. # 1234567.to_s(:human, precision: 4,
  98. # significant: false) # => "1.2346 Million"
  99. # 1234567.to_s(:human, precision: 1,
  100. # separator: ',',
  101. # significant: false) # => "1,2 Million"
  102. 1 def to_s(format = nil, options = nil)
  103. 101 case format
  104. when nil
  105. 101 super()
  106. when Integer, String
  107. super(format)
  108. when :phone
  109. ActiveSupport::NumberHelper.number_to_phone(self, options || {})
  110. when :currency
  111. ActiveSupport::NumberHelper.number_to_currency(self, options || {})
  112. when :percentage
  113. ActiveSupport::NumberHelper.number_to_percentage(self, options || {})
  114. when :delimited
  115. ActiveSupport::NumberHelper.number_to_delimited(self, options || {})
  116. when :rounded
  117. ActiveSupport::NumberHelper.number_to_rounded(self, options || {})
  118. when :human
  119. ActiveSupport::NumberHelper.number_to_human(self, options || {})
  120. when :human_size
  121. ActiveSupport::NumberHelper.number_to_human_size(self, options || {})
  122. when Symbol
  123. super()
  124. else
  125. super(format)
  126. end
  127. end
  128. end
  129. # Ruby 2.4+ unifies Fixnum & Bignum into Integer.
  130. 1 if 0.class == Integer
  131. 1 Integer.prepend ActiveSupport::NumericWithFormat
  132. else
  133. Fixnum.prepend ActiveSupport::NumericWithFormat
  134. Bignum.prepend ActiveSupport::NumericWithFormat
  135. end
  136. 1 Float.prepend ActiveSupport::NumericWithFormat
  137. 1 BigDecimal.prepend ActiveSupport::NumericWithFormat

target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/numeric/inquiry.rb

11.11% lines covered

9 relevant lines. 1 lines covered and 8 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 unless 1.respond_to?(:positive?) # TODO: Remove this file when we drop support to ruby < 2.3
  3. class Numeric
  4. # Returns true if the number is positive.
  5. #
  6. # 1.positive? # => true
  7. # 0.positive? # => false
  8. # -1.positive? # => false
  9. def positive?
  10. self > 0
  11. end
  12. # Returns true if the number is negative.
  13. #
  14. # -1.negative? # => true
  15. # 0.negative? # => false
  16. # 1.negative? # => false
  17. def negative?
  18. self < 0
  19. end
  20. end
  21. class Complex
  22. undef :positive?
  23. undef :negative?
  24. end
  25. end

target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/securerandom.rb

50.0% lines covered

8 relevant lines. 4 lines covered and 4 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "securerandom"
  3. 1 module SecureRandom
  4. 1 BASE58_ALPHABET = ("0".."9").to_a + ("A".."Z").to_a + ("a".."z").to_a - ["0", "O", "I", "l"]
  5. # SecureRandom.base58 generates a random base58 string.
  6. #
  7. # The argument _n_ specifies the length, of the random string to be generated.
  8. #
  9. # If _n_ is not specified or is +nil+, 16 is assumed. It may be larger in the future.
  10. #
  11. # The result may contain alphanumeric characters except 0, O, I and l
  12. #
  13. # p SecureRandom.base58 # => "4kUgL2pdQMSCQtjE"
  14. # p SecureRandom.base58(24) # => "77TMHrHJFvFDwodq8w7Ev2m7"
  15. #
  16. 1 def self.base58(n = 16)
  17. SecureRandom.random_bytes(n).unpack("C*").map do |byte|
  18. idx = byte % 64
  19. idx = SecureRandom.random_number(58) if idx >= 58
  20. BASE58_ALPHABET[idx]
  21. end.join
  22. end
  23. end

target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/string.rb

100.0% lines covered

13 relevant lines. 13 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/string/conversions"
  3. 1 require "active_support/core_ext/string/filters"
  4. 1 require "active_support/core_ext/string/multibyte"
  5. 1 require "active_support/core_ext/string/starts_ends_with"
  6. 1 require "active_support/core_ext/string/inflections"
  7. 1 require "active_support/core_ext/string/access"
  8. 1 require "active_support/core_ext/string/behavior"
  9. 1 require "active_support/core_ext/string/output_safety"
  10. 1 require "active_support/core_ext/string/exclude"
  11. 1 require "active_support/core_ext/string/strip"
  12. 1 require "active_support/core_ext/string/inquiry"
  13. 1 require "active_support/core_ext/string/indent"
  14. 1 require "active_support/core_ext/string/zones"

target/rubygems/gems/activesupport-5.2.3/lib/active_support/core_ext/string/exclude.rb

66.67% lines covered

3 relevant lines. 2 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class String
  3. # The inverse of <tt>String#include?</tt>. Returns true if the string
  4. # does not include the other string.
  5. #
  6. # "hello".exclude? "lo" # => false
  7. # "hello".exclude? "ol" # => true
  8. # "hello".exclude? ?h # => false
  9. 1 def exclude?(string)
  10. !include?(string)
  11. end
  12. end

target/rubygems/gems/activesupport-5.2.3/lib/active_support/current_attributes.rb

47.06% lines covered

51 relevant lines. 24 lines covered and 27 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveSupport
  3. # Abstract super class that provides a thread-isolated attributes singleton, which resets automatically
  4. # before and after each request. This allows you to keep all the per-request attributes easily
  5. # available to the whole system.
  6. #
  7. # The following full app-like example demonstrates how to use a Current class to
  8. # facilitate easy access to the global, per-request attributes without passing them deeply
  9. # around everywhere:
  10. #
  11. # # app/models/current.rb
  12. # class Current < ActiveSupport::CurrentAttributes
  13. # attribute :account, :user
  14. # attribute :request_id, :user_agent, :ip_address
  15. #
  16. # resets { Time.zone = nil }
  17. #
  18. # def user=(user)
  19. # super
  20. # self.account = user.account
  21. # Time.zone = user.time_zone
  22. # end
  23. # end
  24. #
  25. # # app/controllers/concerns/authentication.rb
  26. # module Authentication
  27. # extend ActiveSupport::Concern
  28. #
  29. # included do
  30. # before_action :authenticate
  31. # end
  32. #
  33. # private
  34. # def authenticate
  35. # if authenticated_user = User.find_by(id: cookies.encrypted[:user_id])
  36. # Current.user = authenticated_user
  37. # else
  38. # redirect_to new_session_url
  39. # end
  40. # end
  41. # end
  42. #
  43. # # app/controllers/concerns/set_current_request_details.rb
  44. # module SetCurrentRequestDetails
  45. # extend ActiveSupport::Concern
  46. #
  47. # included do
  48. # before_action do
  49. # Current.request_id = request.uuid
  50. # Current.user_agent = request.user_agent
  51. # Current.ip_address = request.ip
  52. # end
  53. # end
  54. # end
  55. #
  56. # class ApplicationController < ActionController::Base
  57. # include Authentication
  58. # include SetCurrentRequestDetails
  59. # end
  60. #
  61. # class MessagesController < ApplicationController
  62. # def create
  63. # Current.account.messages.create(message_params)
  64. # end
  65. # end
  66. #
  67. # class Message < ApplicationRecord
  68. # belongs_to :creator, default: -> { Current.user }
  69. # after_create { |message| Event.create(record: message) }
  70. # end
  71. #
  72. # class Event < ApplicationRecord
  73. # before_create do
  74. # self.request_id = Current.request_id
  75. # self.user_agent = Current.user_agent
  76. # self.ip_address = Current.ip_address
  77. # end
  78. # end
  79. #
  80. # A word of caution: It's easy to overdo a global singleton like Current and tangle your model as a result.
  81. # Current should only be used for a few, top-level globals, like account, user, and request details.
  82. # The attributes stuck in Current should be used by more or less all actions on all requests. If you start
  83. # sticking controller-specific attributes in there, you're going to create a mess.
  84. 1 class CurrentAttributes
  85. 1 include ActiveSupport::Callbacks
  86. 1 define_callbacks :reset
  87. 1 class << self
  88. # Returns singleton instance for this class in this thread. If none exists, one is created.
  89. 1 def instance
  90. current_instances[name] ||= new
  91. end
  92. # Declares one or more attributes that will be given both class and instance accessor methods.
  93. 1 def attribute(*names)
  94. generated_attribute_methods.module_eval do
  95. names.each do |name|
  96. define_method(name) do
  97. attributes[name.to_sym]
  98. end
  99. define_method("#{name}=") do |attribute|
  100. attributes[name.to_sym] = attribute
  101. end
  102. end
  103. end
  104. names.each do |name|
  105. define_singleton_method(name) do
  106. instance.public_send(name)
  107. end
  108. define_singleton_method("#{name}=") do |attribute|
  109. instance.public_send("#{name}=", attribute)
  110. end
  111. end
  112. end
  113. # Calls this block after #reset is called on the instance. Used for resetting external collaborators, like Time.zone.
  114. 1 def resets(&block)
  115. set_callback :reset, :after, &block
  116. end
  117. 1 delegate :set, :reset, to: :instance
  118. 1 def reset_all # :nodoc:
  119. 4 current_instances.each_value(&:reset)
  120. end
  121. 1 def clear_all # :nodoc:
  122. reset_all
  123. current_instances.clear
  124. end
  125. 1 private
  126. 1 def generated_attribute_methods
  127. @generated_attribute_methods ||= Module.new.tap { |mod| include mod }
  128. end
  129. 1 def current_instances
  130. 4 Thread.current[:current_attributes_instances] ||= {}
  131. end
  132. 1 def method_missing(name, *args, &block)
  133. # Caches the method definition as a singleton method of the receiver.
  134. #
  135. # By letting #delegate handle it, we avoid an enclosure that'll capture args.
  136. singleton_class.delegate name, to: :instance
  137. send(name, *args, &block)
  138. end
  139. end
  140. 1 attr_accessor :attributes
  141. 1 def initialize
  142. @attributes = {}
  143. end
  144. # Expose one or more attributes within a block. Old values are returned after the block concludes.
  145. # Example demonstrating the common use of needing to set Current attributes outside the request-cycle:
  146. #
  147. # class Chat::PublicationJob < ApplicationJob
  148. # def perform(attributes, room_number, creator)
  149. # Current.set(person: creator) do
  150. # Chat::Publisher.publish(attributes: attributes, room_number: room_number)
  151. # end
  152. # end
  153. # end
  154. 1 def set(set_attributes)
  155. old_attributes = compute_attributes(set_attributes.keys)
  156. assign_attributes(set_attributes)
  157. yield
  158. ensure
  159. assign_attributes(old_attributes)
  160. end
  161. # Reset all attributes. Should be called before and after actions, when used as a per-request singleton.
  162. 1 def reset
  163. run_callbacks :reset do
  164. self.attributes = {}
  165. end
  166. end
  167. 1 private
  168. 1 def assign_attributes(new_attributes)
  169. new_attributes.each { |key, value| public_send("#{key}=", value) }
  170. end
  171. 1 def compute_attributes(keys)
  172. keys.collect { |key| [ key, public_send(key) ] }.to_h
  173. end
  174. end
  175. end

target/rubygems/gems/activesupport-5.2.3/lib/active_support/digest.rb

80.0% lines covered

10 relevant lines. 8 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveSupport
  3. 1 class Digest #:nodoc:
  4. 1 class <<self
  5. 1 def hash_digest_class
  6. @hash_digest_class ||= ::Digest::MD5
  7. end
  8. 1 def hash_digest_class=(klass)
  9. 1 raise ArgumentError, "#{klass} is expected to implement hexdigest class method" unless klass.respond_to?(:hexdigest)
  10. 1 @hash_digest_class = klass
  11. end
  12. 1 def hexdigest(arg)
  13. hash_digest_class.hexdigest(arg)[0...32]
  14. end
  15. end
  16. end
  17. end

target/rubygems/gems/activesupport-5.2.3/lib/active_support/test_case.rb

97.62% lines covered

42 relevant lines. 41 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 gem "minitest" # make sure we get the gem, not stdlib
  3. 1 require "minitest"
  4. 1 require "active_support/testing/tagged_logging"
  5. 1 require "active_support/testing/setup_and_teardown"
  6. 1 require "active_support/testing/assertions"
  7. 1 require "active_support/testing/deprecation"
  8. 1 require "active_support/testing/declarative"
  9. 1 require "active_support/testing/isolation"
  10. 1 require "active_support/testing/constant_lookup"
  11. 1 require "active_support/testing/time_helpers"
  12. 1 require "active_support/testing/file_fixtures"
  13. 1 module ActiveSupport
  14. 1 class TestCase < ::Minitest::Test
  15. 1 Assertion = Minitest::Assertion
  16. 1 class << self
  17. # Sets the order in which test cases are run.
  18. #
  19. # ActiveSupport::TestCase.test_order = :random # => :random
  20. #
  21. # Valid values are:
  22. # * +:random+ (to run tests in random order)
  23. # * +:parallel+ (to run tests in parallel)
  24. # * +:sorted+ (to run tests alphabetically by method name)
  25. # * +:alpha+ (equivalent to +:sorted+)
  26. 1 def test_order=(new_order)
  27. ActiveSupport.test_order = new_order
  28. end
  29. # Returns the order in which test cases are run.
  30. #
  31. # ActiveSupport::TestCase.test_order # => :random
  32. #
  33. # Possible values are +:random+, +:parallel+, +:alpha+, +:sorted+.
  34. # Defaults to +:random+.
  35. 1 def test_order
  36. 8 ActiveSupport.test_order ||= :random
  37. end
  38. end
  39. 1 alias_method :method_name, :name
  40. 1 include ActiveSupport::Testing::TaggedLogging
  41. 1 prepend ActiveSupport::Testing::SetupAndTeardown
  42. 1 include ActiveSupport::Testing::Assertions
  43. 1 include ActiveSupport::Testing::Deprecation
  44. 1 include ActiveSupport::Testing::TimeHelpers
  45. 1 include ActiveSupport::Testing::FileFixtures
  46. 1 extend ActiveSupport::Testing::Declarative
  47. # test/unit backwards compatibility methods
  48. 1 alias :assert_raise :assert_raises
  49. 1 alias :assert_not_empty :refute_empty
  50. 1 alias :assert_not_equal :refute_equal
  51. 1 alias :assert_not_in_delta :refute_in_delta
  52. 1 alias :assert_not_in_epsilon :refute_in_epsilon
  53. 1 alias :assert_not_includes :refute_includes
  54. 1 alias :assert_not_instance_of :refute_instance_of
  55. 1 alias :assert_not_kind_of :refute_kind_of
  56. 1 alias :assert_no_match :refute_match
  57. 1 alias :assert_not_nil :refute_nil
  58. 1 alias :assert_not_operator :refute_operator
  59. 1 alias :assert_not_predicate :refute_predicate
  60. 1 alias :assert_not_respond_to :refute_respond_to
  61. 1 alias :assert_not_same :refute_same
  62. 1 ActiveSupport.run_load_hooks(:active_support_test_case, self)
  63. end
  64. end

target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/assertions.rb

18.52% lines covered

54 relevant lines. 10 lines covered and 44 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveSupport
  3. 1 module Testing
  4. 1 module Assertions
  5. 1 UNTRACKED = Object.new # :nodoc:
  6. # Asserts that an expression is not truthy. Passes if <tt>object</tt> is
  7. # +nil+ or +false+. "Truthy" means "considered true in a conditional"
  8. # like <tt>if foo</tt>.
  9. #
  10. # assert_not nil # => true
  11. # assert_not false # => true
  12. # assert_not 'foo' # => Expected "foo" to be nil or false
  13. #
  14. # An error message can be specified.
  15. #
  16. # assert_not foo, 'foo should be false'
  17. 1 def assert_not(object, message = nil)
  18. message ||= "Expected #{mu_pp(object)} to be nil or false"
  19. assert !object, message
  20. end
  21. # Assertion that the block should not raise an exception.
  22. #
  23. # Passes if evaluated code in the yielded block raises no exception.
  24. #
  25. # assert_nothing_raised do
  26. # perform_service(param: 'no_exception')
  27. # end
  28. 1 def assert_nothing_raised
  29. yield
  30. end
  31. # Test numeric difference between the return value of an expression as a
  32. # result of what is evaluated in the yielded block.
  33. #
  34. # assert_difference 'Article.count' do
  35. # post :create, params: { article: {...} }
  36. # end
  37. #
  38. # An arbitrary expression is passed in and evaluated.
  39. #
  40. # assert_difference 'Article.last.comments(:reload).size' do
  41. # post :create, params: { comment: {...} }
  42. # end
  43. #
  44. # An arbitrary positive or negative difference can be specified.
  45. # The default is <tt>1</tt>.
  46. #
  47. # assert_difference 'Article.count', -1 do
  48. # post :delete, params: { id: ... }
  49. # end
  50. #
  51. # An array of expressions can also be passed in and evaluated.
  52. #
  53. # assert_difference [ 'Article.count', 'Post.count' ], 2 do
  54. # post :create, params: { article: {...} }
  55. # end
  56. #
  57. # A hash of expressions/numeric differences can also be passed in and evaluated.
  58. #
  59. # assert_difference ->{ Article.count } => 1, ->{ Notification.count } => 2 do
  60. # post :create, params: { article: {...} }
  61. # end
  62. #
  63. # A lambda or a list of lambdas can be passed in and evaluated:
  64. #
  65. # assert_difference ->{ Article.count }, 2 do
  66. # post :create, params: { article: {...} }
  67. # end
  68. #
  69. # assert_difference [->{ Article.count }, ->{ Post.count }], 2 do
  70. # post :create, params: { article: {...} }
  71. # end
  72. #
  73. # An error message can be specified.
  74. #
  75. # assert_difference 'Article.count', -1, 'An Article should be destroyed' do
  76. # post :delete, params: { id: ... }
  77. # end
  78. 1 def assert_difference(expression, *args, &block)
  79. expressions =
  80. if expression.is_a?(Hash)
  81. message = args[0]
  82. expression
  83. else
  84. difference = args[0] || 1
  85. message = args[1]
  86. Hash[Array(expression).map { |e| [e, difference] }]
  87. end
  88. exps = expressions.keys.map { |e|
  89. e.respond_to?(:call) ? e : lambda { eval(e, block.binding) }
  90. }
  91. before = exps.map(&:call)
  92. retval = yield
  93. expressions.zip(exps, before) do |(code, diff), exp, before_value|
  94. error = "#{code.inspect} didn't change by #{diff}"
  95. error = "#{message}.\n#{error}" if message
  96. assert_equal(before_value + diff, exp.call, error)
  97. end
  98. retval
  99. end
  100. # Assertion that the numeric result of evaluating an expression is not
  101. # changed before and after invoking the passed in block.
  102. #
  103. # assert_no_difference 'Article.count' do
  104. # post :create, params: { article: invalid_attributes }
  105. # end
  106. #
  107. # An error message can be specified.
  108. #
  109. # assert_no_difference 'Article.count', 'An Article should not be created' do
  110. # post :create, params: { article: invalid_attributes }
  111. # end
  112. 1 def assert_no_difference(expression, message = nil, &block)
  113. assert_difference expression, 0, message, &block
  114. end
  115. # Assertion that the result of evaluating an expression is changed before
  116. # and after invoking the passed in block.
  117. #
  118. # assert_changes 'Status.all_good?' do
  119. # post :create, params: { status: { ok: false } }
  120. # end
  121. #
  122. # You can pass the block as a string to be evaluated in the context of
  123. # the block. A lambda can be passed for the block as well.
  124. #
  125. # assert_changes -> { Status.all_good? } do
  126. # post :create, params: { status: { ok: false } }
  127. # end
  128. #
  129. # The assertion is useful to test side effects. The passed block can be
  130. # anything that can be converted to string with #to_s.
  131. #
  132. # assert_changes :@object do
  133. # @object = 42
  134. # end
  135. #
  136. # The keyword arguments :from and :to can be given to specify the
  137. # expected initial value and the expected value after the block was
  138. # executed.
  139. #
  140. # assert_changes :@object, from: nil, to: :foo do
  141. # @object = :foo
  142. # end
  143. #
  144. # An error message can be specified.
  145. #
  146. # assert_changes -> { Status.all_good? }, 'Expected the status to be bad' do
  147. # post :create, params: { status: { incident: true } }
  148. # end
  149. 1 def assert_changes(expression, message = nil, from: UNTRACKED, to: UNTRACKED, &block)
  150. exp = expression.respond_to?(:call) ? expression : -> { eval(expression.to_s, block.binding) }
  151. before = exp.call
  152. retval = yield
  153. unless from == UNTRACKED
  154. error = "#{expression.inspect} isn't #{from.inspect}"
  155. error = "#{message}.\n#{error}" if message
  156. assert from === before, error
  157. end
  158. after = exp.call
  159. error = "#{expression.inspect} didn't change"
  160. error = "#{error}. It was already #{to}" if before == to
  161. error = "#{message}.\n#{error}" if message
  162. assert before != after, error
  163. unless to == UNTRACKED
  164. error = "#{expression.inspect} didn't change to #{to}"
  165. error = "#{message}.\n#{error}" if message
  166. assert to === after, error
  167. end
  168. retval
  169. end
  170. # Assertion that the result of evaluating an expression is not changed before
  171. # and after invoking the passed in block.
  172. #
  173. # assert_no_changes 'Status.all_good?' do
  174. # post :create, params: { status: { ok: true } }
  175. # end
  176. #
  177. # An error message can be specified.
  178. #
  179. # assert_no_changes -> { Status.all_good? }, 'Expected the status to be good' do
  180. # post :create, params: { status: { ok: false } }
  181. # end
  182. 1 def assert_no_changes(expression, message = nil, &block)
  183. exp = expression.respond_to?(:call) ? expression : -> { eval(expression.to_s, block.binding) }
  184. before = exp.call
  185. retval = yield
  186. after = exp.call
  187. error = "#{expression.inspect} did change to #{after}"
  188. error = "#{message}.\n#{error}" if message
  189. assert before == after, error
  190. retval
  191. end
  192. end
  193. end
  194. end

target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/autorun.rb

100.0% lines covered

3 relevant lines. 3 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 gem "minitest"
  3. 1 require "minitest"
  4. 1 Minitest.autorun

target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/constant_lookup.rb

53.33% lines covered

15 relevant lines. 8 lines covered and 7 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/concern"
  3. 1 require "active_support/inflector"
  4. 1 module ActiveSupport
  5. 1 module Testing
  6. # Resolves a constant from a minitest spec name.
  7. #
  8. # Given the following spec-style test:
  9. #
  10. # describe WidgetsController, :index do
  11. # describe "authenticated user" do
  12. # describe "returns widgets" do
  13. # it "has a controller that exists" do
  14. # assert_kind_of WidgetsController, @controller
  15. # end
  16. # end
  17. # end
  18. # end
  19. #
  20. # The test will have the following name:
  21. #
  22. # "WidgetsController::index::authenticated user::returns widgets"
  23. #
  24. # The constant WidgetsController can be resolved from the name.
  25. # The following code will resolve the constant:
  26. #
  27. # controller = determine_constant_from_test_name(name) do |constant|
  28. # Class === constant && constant < ::ActionController::Metal
  29. # end
  30. 1 module ConstantLookup
  31. 1 extend ::ActiveSupport::Concern
  32. 1 module ClassMethods # :nodoc:
  33. 1 def determine_constant_from_test_name(test_name)
  34. names = test_name.split "::"
  35. while names.size > 0 do
  36. names.last.sub!(/Test$/, "")
  37. begin
  38. constant = names.join("::").safe_constantize
  39. break(constant) if yield(constant)
  40. ensure
  41. names.pop
  42. end
  43. end
  44. end
  45. end
  46. end
  47. end
  48. end

target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/declarative.rb

83.33% lines covered

12 relevant lines. 10 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveSupport
  3. 1 module Testing
  4. 1 module Declarative
  5. 1 unless defined?(Spec)
  6. # Helper to define a test method using a String. Under the hood, it replaces
  7. # spaces with underscores and defines the test method.
  8. #
  9. # test "verify something" do
  10. # ...
  11. # end
  12. 1 def test(name, &block)
  13. 2 test_name = "test_#{name.gsub(/\s+/, '_')}".to_sym
  14. 2 defined = method_defined? test_name
  15. 2 raise "#{test_name} is already defined in #{self}" if defined
  16. 2 if block_given?
  17. 2 define_method(test_name, &block)
  18. else
  19. define_method(test_name) do
  20. flunk "No implementation provided for #{name}"
  21. end
  22. end
  23. end
  24. end
  25. end
  26. end
  27. end

target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/deprecation.rb

32.0% lines covered

25 relevant lines. 8 lines covered and 17 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/deprecation"
  3. 1 require "active_support/core_ext/regexp"
  4. 1 module ActiveSupport
  5. 1 module Testing
  6. 1 module Deprecation #:nodoc:
  7. 1 def assert_deprecated(match = nil, deprecator = nil, &block)
  8. result, warnings = collect_deprecations(deprecator, &block)
  9. assert !warnings.empty?, "Expected a deprecation warning within the block but received none"
  10. if match
  11. match = Regexp.new(Regexp.escape(match)) unless match.is_a?(Regexp)
  12. assert warnings.any? { |w| match.match?(w) }, "No deprecation warning matched #{match}: #{warnings.join(', ')}"
  13. end
  14. result
  15. end
  16. 1 def assert_not_deprecated(deprecator = nil, &block)
  17. result, deprecations = collect_deprecations(deprecator, &block)
  18. assert deprecations.empty?, "Expected no deprecation warning within the block but received #{deprecations.size}: \n #{deprecations * "\n "}"
  19. result
  20. end
  21. 1 def collect_deprecations(deprecator = nil)
  22. deprecator ||= ActiveSupport::Deprecation
  23. old_behavior = deprecator.behavior
  24. deprecations = []
  25. deprecator.behavior = Proc.new do |message, callstack|
  26. deprecations << message
  27. end
  28. result = yield
  29. [result, deprecations]
  30. ensure
  31. deprecator.behavior = old_behavior
  32. end
  33. end
  34. end
  35. end

target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/file_fixtures.rb

58.33% lines covered

12 relevant lines. 7 lines covered and 5 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveSupport
  3. 1 module Testing
  4. # Adds simple access to sample files called file fixtures.
  5. # File fixtures are normal files stored in
  6. # <tt>ActiveSupport::TestCase.file_fixture_path</tt>.
  7. #
  8. # File fixtures are represented as +Pathname+ objects.
  9. # This makes it easy to extract specific information:
  10. #
  11. # file_fixture("example.txt").read # get the file's content
  12. # file_fixture("example.mp3").size # get the file size
  13. 1 module FileFixtures
  14. 1 extend ActiveSupport::Concern
  15. 1 included do
  16. 1 class_attribute :file_fixture_path, instance_writer: false
  17. end
  18. # Returns a +Pathname+ to the fixture file named +fixture_name+.
  19. #
  20. # Raises +ArgumentError+ if +fixture_name+ can't be found.
  21. 1 def file_fixture(fixture_name)
  22. path = Pathname.new(File.join(file_fixture_path, fixture_name))
  23. if path.exist?
  24. path
  25. else
  26. msg = "the directory '%s' does not contain a file named '%s'"
  27. raise ArgumentError, msg % [file_fixture_path, fixture_name]
  28. end
  29. end
  30. end
  31. end
  32. end

target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/isolation.rb

22.95% lines covered

61 relevant lines. 14 lines covered and 47 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveSupport
  3. 1 module Testing
  4. 1 module Isolation
  5. 1 require "thread"
  6. 1 def self.included(klass) #:nodoc:
  7. klass.class_eval do
  8. parallelize_me!
  9. end
  10. end
  11. 1 def self.forking_env?
  12. 1 !ENV["NO_FORK"] && Process.respond_to?(:fork)
  13. end
  14. 1 def run
  15. serialized = run_in_isolation do
  16. super
  17. end
  18. Marshal.load(serialized)
  19. end
  20. 1 module Forking
  21. 1 def run_in_isolation(&blk)
  22. read, write = IO.pipe
  23. read.binmode
  24. write.binmode
  25. pid = fork do
  26. read.close
  27. yield
  28. begin
  29. if error?
  30. failures.map! { |e|
  31. begin
  32. Marshal.dump e
  33. e
  34. rescue TypeError
  35. ex = Exception.new e.message
  36. ex.set_backtrace e.backtrace
  37. Minitest::UnexpectedError.new ex
  38. end
  39. }
  40. end
  41. test_result = defined?(Minitest::Result) ? Minitest::Result.from(self) : dup
  42. result = Marshal.dump(test_result)
  43. end
  44. write.puts [result].pack("m")
  45. exit!
  46. end
  47. write.close
  48. result = read.read
  49. Process.wait2(pid)
  50. result.unpack("m")[0]
  51. end
  52. end
  53. 1 module Subprocess
  54. 1 ORIG_ARGV = ARGV.dup unless defined?(ORIG_ARGV)
  55. # Crazy H4X to get this working in windows / jruby with
  56. # no forking.
  57. 1 def run_in_isolation(&blk)
  58. require "tempfile"
  59. if ENV["ISOLATION_TEST"]
  60. yield
  61. test_result = defined?(Minitest::Result) ? Minitest::Result.from(self) : dup
  62. File.open(ENV["ISOLATION_OUTPUT"], "w") do |file|
  63. file.puts [Marshal.dump(test_result)].pack("m")
  64. end
  65. exit!
  66. else
  67. Tempfile.open("isolation") do |tmpfile|
  68. env = {
  69. "ISOLATION_TEST" => self.class.name,
  70. "ISOLATION_OUTPUT" => tmpfile.path
  71. }
  72. test_opts = "-n#{self.class.name}##{name}"
  73. load_path_args = []
  74. $-I.each do |p|
  75. load_path_args << "-I"
  76. load_path_args << File.expand_path(p)
  77. end
  78. child = IO.popen([env, Gem.ruby, *load_path_args, $0, *ORIG_ARGV, test_opts])
  79. begin
  80. Process.wait(child.pid)
  81. rescue Errno::ECHILD # The child process may exit before we wait
  82. nil
  83. end
  84. return tmpfile.read.unpack("m")[0]
  85. end
  86. end
  87. end
  88. end
  89. 1 include forking_env? ? Forking : Subprocess
  90. end
  91. end
  92. end

target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/setup_and_teardown.rb

95.24% lines covered

21 relevant lines. 20 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/callbacks"
  3. 1 module ActiveSupport
  4. 1 module Testing
  5. # Adds support for +setup+ and +teardown+ callbacks.
  6. # These callbacks serve as a replacement to overwriting the
  7. # <tt>#setup</tt> and <tt>#teardown</tt> methods of your TestCase.
  8. #
  9. # class ExampleTest < ActiveSupport::TestCase
  10. # setup do
  11. # # ...
  12. # end
  13. #
  14. # teardown do
  15. # # ...
  16. # end
  17. # end
  18. 1 module SetupAndTeardown
  19. 1 def self.prepended(klass)
  20. 1 klass.include ActiveSupport::Callbacks
  21. 1 klass.define_callbacks :setup, :teardown
  22. 1 klass.extend ClassMethods
  23. end
  24. 1 module ClassMethods
  25. # Add a callback, which runs before <tt>TestCase#setup</tt>.
  26. 1 def setup(*args, &block)
  27. 5 set_callback(:setup, :before, *args, &block)
  28. end
  29. # Add a callback, which runs after <tt>TestCase#teardown</tt>.
  30. 1 def teardown(*args, &block)
  31. 3 set_callback(:teardown, :after, *args, &block)
  32. end
  33. end
  34. 1 def before_setup # :nodoc:
  35. 2 super
  36. 2 run_callbacks :setup
  37. end
  38. 1 def after_teardown # :nodoc:
  39. 2 begin
  40. 2 run_callbacks :teardown
  41. rescue => e
  42. self.failures << Minitest::UnexpectedError.new(e)
  43. end
  44. 2 super
  45. end
  46. end
  47. end
  48. end

target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/stream.rb

25.93% lines covered

27 relevant lines. 7 lines covered and 20 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveSupport
  3. 1 module Testing
  4. 1 module Stream #:nodoc:
  5. 1 private
  6. 1 def silence_stream(stream)
  7. old_stream = stream.dup
  8. stream.reopen(IO::NULL)
  9. stream.sync = true
  10. yield
  11. ensure
  12. stream.reopen(old_stream)
  13. old_stream.close
  14. end
  15. 1 def quietly
  16. silence_stream(STDOUT) do
  17. silence_stream(STDERR) do
  18. yield
  19. end
  20. end
  21. end
  22. 1 def capture(stream)
  23. stream = stream.to_s
  24. captured_stream = Tempfile.new(stream)
  25. stream_io = eval("$#{stream}")
  26. origin_stream = stream_io.dup
  27. stream_io.reopen(captured_stream)
  28. yield
  29. stream_io.rewind
  30. return captured_stream.read
  31. ensure
  32. captured_stream.close
  33. captured_stream.unlink
  34. stream_io.reopen(origin_stream)
  35. end
  36. end
  37. end
  38. end

target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/tagged_logging.rb

100.0% lines covered

15 relevant lines. 15 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module ActiveSupport
  3. 1 module Testing
  4. # Logs a "PostsControllerTest: test name" heading before each test to
  5. # make test.log easier to search and follow along with.
  6. 1 module TaggedLogging #:nodoc:
  7. 1 attr_writer :tagged_logger
  8. 1 def before_setup
  9. 2 if tagged_logger && tagged_logger.info?
  10. 2 heading = "#{self.class}: #{name}"
  11. 2 divider = "-" * heading.size
  12. 2 tagged_logger.info divider
  13. 2 tagged_logger.info heading
  14. 2 tagged_logger.info divider
  15. end
  16. 2 super
  17. end
  18. 1 private
  19. 1 def tagged_logger
  20. 10 @tagged_logger ||= (defined?(Rails.logger) && Rails.logger)
  21. end
  22. end
  23. end
  24. end

target/rubygems/gems/activesupport-5.2.3/lib/active_support/testing/time_helpers.rb

50.88% lines covered

57 relevant lines. 29 lines covered and 28 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/module/redefine_method"
  3. 1 require "active_support/core_ext/string/strip" # for strip_heredoc
  4. 1 require "active_support/core_ext/time/calculations"
  5. 1 require "concurrent/map"
  6. 1 module ActiveSupport
  7. 1 module Testing
  8. 1 class SimpleStubs # :nodoc:
  9. 1 Stub = Struct.new(:object, :method_name, :original_method)
  10. 1 def initialize
  11. 2 @stubs = Concurrent::Map.new { |h, k| h[k] = {} }
  12. end
  13. 1 def stub_object(object, method_name, &block)
  14. if stub = stubbing(object, method_name)
  15. unstub_object(stub)
  16. end
  17. new_name = "__simple_stub__#{method_name}"
  18. @stubs[object.object_id][method_name] = Stub.new(object, method_name, new_name)
  19. object.singleton_class.send :alias_method, new_name, method_name
  20. object.define_singleton_method(method_name, &block)
  21. end
  22. 1 def unstub_all!
  23. 2 @stubs.each_value do |object_stubs|
  24. object_stubs.each_value do |stub|
  25. unstub_object(stub)
  26. end
  27. end
  28. 2 @stubs.clear
  29. end
  30. 1 def stubbing(object, method_name)
  31. @stubs[object.object_id][method_name]
  32. end
  33. 1 private
  34. 1 def unstub_object(stub)
  35. singleton_class = stub.object.singleton_class
  36. singleton_class.send :silence_redefinition_of_method, stub.method_name
  37. singleton_class.send :alias_method, stub.method_name, stub.original_method
  38. singleton_class.send :undef_method, stub.original_method
  39. end
  40. end
  41. # Contains helpers that help you test passage of time.
  42. 1 module TimeHelpers
  43. 1 def after_teardown
  44. 2 travel_back
  45. 2 super
  46. end
  47. # Changes current time to the time in the future or in the past by a given time difference by
  48. # stubbing +Time.now+, +Date.today+, and +DateTime.now+. The stubs are automatically removed
  49. # at the end of the test.
  50. #
  51. # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
  52. # travel 1.day
  53. # Time.current # => Sun, 10 Nov 2013 15:34:49 EST -05:00
  54. # Date.current # => Sun, 10 Nov 2013
  55. # DateTime.current # => Sun, 10 Nov 2013 15:34:49 -0500
  56. #
  57. # This method also accepts a block, which will return the current time back to its original
  58. # state at the end of the block:
  59. #
  60. # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
  61. # travel 1.day do
  62. # User.create.created_at # => Sun, 10 Nov 2013 15:34:49 EST -05:00
  63. # end
  64. # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
  65. 1 def travel(duration, &block)
  66. travel_to Time.now + duration, &block
  67. end
  68. # Changes current time to the given time by stubbing +Time.now+,
  69. # +Date.today+, and +DateTime.now+ to return the time or date passed into this method.
  70. # The stubs are automatically removed at the end of the test.
  71. #
  72. # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
  73. # travel_to Time.zone.local(2004, 11, 24, 01, 04, 44)
  74. # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
  75. # Date.current # => Wed, 24 Nov 2004
  76. # DateTime.current # => Wed, 24 Nov 2004 01:04:44 -0500
  77. #
  78. # Dates are taken as their timestamp at the beginning of the day in the
  79. # application time zone. <tt>Time.current</tt> returns said timestamp,
  80. # and <tt>Time.now</tt> its equivalent in the system time zone. Similarly,
  81. # <tt>Date.current</tt> returns a date equal to the argument, and
  82. # <tt>Date.today</tt> the date according to <tt>Time.now</tt>, which may
  83. # be different. (Note that you rarely want to deal with <tt>Time.now</tt>,
  84. # or <tt>Date.today</tt>, in order to honor the application time zone
  85. # please always use <tt>Time.current</tt> and <tt>Date.current</tt>.)
  86. #
  87. # Note that the usec for the time passed will be set to 0 to prevent rounding
  88. # errors with external services, like MySQL (which will round instead of floor,
  89. # leading to off-by-one-second errors).
  90. #
  91. # This method also accepts a block, which will return the current time back to its original
  92. # state at the end of the block:
  93. #
  94. # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
  95. # travel_to Time.zone.local(2004, 11, 24, 01, 04, 44) do
  96. # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
  97. # end
  98. # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
  99. 1 def travel_to(date_or_time)
  100. if block_given? && simple_stubs.stubbing(Time, :now)
  101. travel_to_nested_block_call = <<-MSG.strip_heredoc
  102. Calling `travel_to` with a block, when we have previously already made a call to `travel_to`, can lead to confusing time stubbing.
  103. Instead of:
  104. travel_to 2.days.from_now do
  105. # 2 days from today
  106. travel_to 3.days.from_now do
  107. # 5 days from today
  108. end
  109. end
  110. preferred way to achieve above is:
  111. travel 2.days do
  112. # 2 days from today
  113. end
  114. travel 5.days do
  115. # 5 days from today
  116. end
  117. MSG
  118. raise travel_to_nested_block_call
  119. end
  120. if date_or_time.is_a?(Date) && !date_or_time.is_a?(DateTime)
  121. now = date_or_time.midnight.to_time
  122. else
  123. now = date_or_time.to_time.change(usec: 0)
  124. end
  125. simple_stubs.stub_object(Time, :now) { at(now.to_i) }
  126. simple_stubs.stub_object(Date, :today) { jd(now.to_date.jd) }
  127. simple_stubs.stub_object(DateTime, :now) { jd(now.to_date.jd, now.hour, now.min, now.sec, Rational(now.utc_offset, 86400)) }
  128. if block_given?
  129. begin
  130. yield
  131. ensure
  132. travel_back
  133. end
  134. end
  135. end
  136. # Returns the current time back to its original state, by removing the stubs added by
  137. # +travel+ and +travel_to+.
  138. #
  139. # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
  140. # travel_to Time.zone.local(2004, 11, 24, 01, 04, 44)
  141. # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
  142. # travel_back
  143. # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
  144. 1 def travel_back
  145. 2 simple_stubs.unstub_all!
  146. end
  147. # Calls +travel_to+ with +Time.now+.
  148. #
  149. # Time.current # => Sun, 09 Jul 2017 15:34:49 EST -05:00
  150. # freeze_time
  151. # sleep(1)
  152. # Time.current # => Sun, 09 Jul 2017 15:34:49 EST -05:00
  153. #
  154. # This method also accepts a block, which will return the current time back to its original
  155. # state at the end of the block:
  156. #
  157. # Time.current # => Sun, 09 Jul 2017 15:34:49 EST -05:00
  158. # freeze_time do
  159. # sleep(1)
  160. # User.create.created_at # => Sun, 09 Jul 2017 15:34:49 EST -05:00
  161. # end
  162. # Time.current # => Sun, 09 Jul 2017 15:34:50 EST -05:00
  163. 1 def freeze_time(&block)
  164. travel_to Time.now, &block
  165. end
  166. 1 private
  167. 1 def simple_stubs
  168. 2 @simple_stubs ||= SimpleStubs.new
  169. end
  170. end
  171. end
  172. end

target/rubygems/gems/devise-4.7.1/app/controllers/devise_controller.rb

39.76% lines covered

83 relevant lines. 33 lines covered and 50 lines missed.
    
  1. # frozen_string_literal: true
  2. # All Devise controllers are inherited from here.
  3. 1 class DeviseController < Devise.parent_controller.constantize
  4. 1 include Devise::Controllers::ScopedViews
  5. 1 if respond_to?(:helper)
  6. 1 helper DeviseHelper
  7. end
  8. 1 if respond_to?(:helper_method)
  9. 1 helpers = %w(resource scope_name resource_name signed_in_resource
  10. resource_class resource_params devise_mapping)
  11. 1 helper_method(*helpers)
  12. end
  13. 1 prepend_before_action :assert_is_devise_resource!
  14. 1 respond_to :html if mimes_for_respond_to.empty?
  15. # Override prefixes to consider the scoped view.
  16. # Notice we need to check for the request due to a bug in
  17. # Action Controller tests that forces _prefixes to be
  18. # loaded before even having a request object.
  19. #
  20. # This method should be public as it is in ActionPack
  21. # itself. Changing its visibility may break other gems.
  22. 1 def _prefixes #:nodoc:
  23. @_prefixes ||= if self.class.scoped_views? && request && devise_mapping
  24. ["#{devise_mapping.scoped_path}/#{controller_name}"] + super
  25. else
  26. super
  27. end
  28. end
  29. 1 protected
  30. # Gets the actual resource stored in the instance variable
  31. 1 def resource
  32. instance_variable_get(:"@#{resource_name}")
  33. end
  34. # Proxy to devise map name
  35. 1 def resource_name
  36. devise_mapping.name
  37. end
  38. 1 alias :scope_name :resource_name
  39. # Proxy to devise map class
  40. 1 def resource_class
  41. devise_mapping.to
  42. end
  43. # Returns a signed in resource from session (if one exists)
  44. 1 def signed_in_resource
  45. warden.authenticate(scope: resource_name)
  46. end
  47. # Attempt to find the mapped route for devise based on request path
  48. 1 def devise_mapping
  49. @devise_mapping ||= request.env["devise.mapping"]
  50. end
  51. # Checks whether it's a devise mapped resource or not.
  52. 1 def assert_is_devise_resource! #:nodoc:
  53. unknown_action! <<-MESSAGE unless devise_mapping
  54. Could not find devise mapping for path #{request.fullpath.inspect}.
  55. This may happen for two reasons:
  56. 1) You forgot to wrap your route inside the scope block. For example:
  57. devise_scope :user do
  58. get "/some/route" => "some_devise_controller"
  59. end
  60. 2) You are testing a Devise controller bypassing the router.
  61. If so, you can explicitly tell Devise which mapping to use:
  62. @request.env["devise.mapping"] = Devise.mappings[:user]
  63. MESSAGE
  64. end
  65. # Returns real navigational formats which are supported by Rails
  66. 1 def navigational_formats
  67. @navigational_formats ||= Devise.navigational_formats.select { |format| Mime::EXTENSION_LOOKUP[format.to_s] }
  68. end
  69. 1 def unknown_action!(msg)
  70. logger.debug "[Devise] #{msg}" if logger
  71. raise AbstractController::ActionNotFound, msg
  72. end
  73. # Sets the resource creating an instance variable
  74. 1 def resource=(new_resource)
  75. instance_variable_set(:"@#{resource_name}", new_resource)
  76. end
  77. # Helper for use in before_actions where no authentication is required.
  78. #
  79. # Example:
  80. # before_action :require_no_authentication, only: :new
  81. 1 def require_no_authentication
  82. assert_is_devise_resource!
  83. return unless is_navigational_format?
  84. no_input = devise_mapping.no_input_strategies
  85. authenticated = if no_input.present?
  86. args = no_input.dup.push scope: resource_name
  87. warden.authenticate?(*args)
  88. else
  89. warden.authenticated?(resource_name)
  90. end
  91. if authenticated && resource = warden.user(resource_name)
  92. flash[:alert] = I18n.t("devise.failure.already_authenticated")
  93. redirect_to after_sign_in_path_for(resource)
  94. end
  95. end
  96. # Helper for use after calling send_*_instructions methods on a resource.
  97. # If we are in paranoid mode, we always act as if the resource was valid
  98. # and instructions were sent.
  99. 1 def successfully_sent?(resource)
  100. notice = if Devise.paranoid
  101. resource.errors.clear
  102. :send_paranoid_instructions
  103. elsif resource.errors.empty?
  104. :send_instructions
  105. end
  106. if notice
  107. set_flash_message! :notice, notice
  108. true
  109. end
  110. end
  111. # Sets the flash message with :key, using I18n. By default you are able
  112. # to set up your messages using specific resource scope, and if no message is
  113. # found we look to the default scope. Set the "now" options key to a true
  114. # value to populate the flash.now hash in lieu of the default flash hash (so
  115. # the flash message will be available to the current action instead of the
  116. # next action).
  117. # Example (i18n locale file):
  118. #
  119. # en:
  120. # devise:
  121. # passwords:
  122. # #default_scope_messages - only if resource_scope is not found
  123. # user:
  124. # #resource_scope_messages
  125. #
  126. # Please refer to README or en.yml locale file to check what messages are
  127. # available.
  128. 1 def set_flash_message(key, kind, options = {})
  129. message = find_message(kind, options)
  130. if options[:now]
  131. flash.now[key] = message if message.present?
  132. else
  133. flash[key] = message if message.present?
  134. end
  135. end
  136. # Sets flash message if is_flashing_format? equals true
  137. 1 def set_flash_message!(key, kind, options = {})
  138. if is_flashing_format?
  139. set_flash_message(key, kind, options)
  140. end
  141. end
  142. # Sets minimum password length to show to user
  143. 1 def set_minimum_password_length
  144. if devise_mapping.validatable?
  145. @minimum_password_length = resource_class.password_length.min
  146. end
  147. end
  148. 1 def devise_i18n_options(options)
  149. options
  150. end
  151. # Get message for given
  152. 1 def find_message(kind, options = {})
  153. options[:scope] ||= translation_scope
  154. options[:default] = Array(options[:default]).unshift(kind.to_sym)
  155. options[:resource_name] = resource_name
  156. options = devise_i18n_options(options)
  157. I18n.t("#{options[:resource_name]}.#{kind}", options)
  158. end
  159. # Controllers inheriting DeviseController are advised to override this
  160. # method so that other controllers inheriting from them would use
  161. # existing translations.
  162. 1 def translation_scope
  163. "devise.#{controller_name}"
  164. end
  165. 1 def clean_up_passwords(object)
  166. object.clean_up_passwords if object.respond_to?(:clean_up_passwords)
  167. end
  168. 1 def respond_with_navigational(*args, &block)
  169. respond_with(*args) do |format|
  170. format.any(*navigational_formats, &block)
  171. end
  172. end
  173. 1 def resource_params
  174. params.fetch(resource_name, {})
  175. end
  176. 1 ActiveSupport.run_load_hooks(:devise_controller, self)
  177. end

target/rubygems/gems/devise-4.7.1/app/helpers/devise_helper.rb

40.0% lines covered

5 relevant lines. 2 lines covered and 3 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module DeviseHelper
  3. # Retain this method for backwards compatibility, deprecated in favour of modifying the
  4. # devise/shared/error_messages partial
  5. 1 def devise_error_messages!
  6. ActiveSupport::Deprecation.warn <<-DEPRECATION.strip_heredoc
  7. [Devise] `DeviseHelper.devise_error_messages!`
  8. is deprecated and it will be removed in the next major version.
  9. To customize the errors styles please run `rails g devise:views` and modify the
  10. `devise/shared/error_messages` partial.
  11. DEPRECATION
  12. return "" if resource.errors.empty?
  13. render "devise/shared/error_messages", resource: resource
  14. end
  15. end

target/rubygems/gems/devise-4.7.1/lib/devise/controllers/scoped_views.rb

77.78% lines covered

9 relevant lines. 7 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Devise
  3. 1 module Controllers
  4. 1 module ScopedViews
  5. 1 extend ActiveSupport::Concern
  6. 1 module ClassMethods
  7. 1 def scoped_views?
  8. defined?(@scoped_views) ? @scoped_views : Devise.scoped_views
  9. end
  10. 1 def scoped_views=(value)
  11. @scoped_views = value
  12. end
  13. end
  14. end
  15. end
  16. end

target/rubygems/gems/devise-4.7.1/lib/devise/controllers/url_helpers.rb

79.17% lines covered

24 relevant lines. 19 lines covered and 5 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Devise
  3. 1 module Controllers
  4. # Create url helpers to be used with resource/scope configuration. Acts as
  5. # proxies to the generated routes created by devise.
  6. # Resource param can be a string or symbol, a class, or an instance object.
  7. # Example using a :user resource:
  8. #
  9. # new_session_path(:user) => new_user_session_path
  10. # session_path(:user) => user_session_path
  11. # destroy_session_path(:user) => destroy_user_session_path
  12. #
  13. # new_password_path(:user) => new_user_password_path
  14. # password_path(:user) => user_password_path
  15. # edit_password_path(:user) => edit_user_password_path
  16. #
  17. # new_confirmation_path(:user) => new_user_confirmation_path
  18. # confirmation_path(:user) => user_confirmation_path
  19. #
  20. # Those helpers are included by default to ActionController::Base.
  21. #
  22. # In case you want to add such helpers to another class, you can do
  23. # that as long as this new class includes both url_helpers and
  24. # mounted_helpers. Example:
  25. #
  26. # include Rails.application.routes.url_helpers
  27. # include Rails.application.routes.mounted_helpers
  28. #
  29. 1 module UrlHelpers
  30. 1 def self.remove_helpers!
  31. 2 self.instance_methods.map(&:to_s).grep(/_(url|path)$/).each do |method|
  32. 48 remove_method method
  33. end
  34. end
  35. 1 def self.generate_helpers!(routes=nil)
  36. 3 routes ||= begin
  37. 2 mappings = Devise.mappings.values.map(&:used_helpers).flatten.uniq
  38. 2 Devise::URL_HELPERS.slice(*mappings)
  39. end
  40. 3 routes.each do |module_name, actions|
  41. 12 [:path, :url].each do |path_or_url|
  42. 24 actions.each do |action|
  43. 68 action = action ? "#{action}_" : ""
  44. 68 method = :"#{action}#{module_name}_#{path_or_url}"
  45. 68 define_method method do |resource_or_scope, *args|
  46. scope = Devise::Mapping.find_scope!(resource_or_scope)
  47. router_name = Devise.mappings[scope].router_name
  48. context = router_name ? send(router_name) : _devise_route_context
  49. context.send("#{action}#{scope}_#{module_name}_#{path_or_url}", *args)
  50. end
  51. end
  52. end
  53. end
  54. end
  55. 1 generate_helpers!(Devise::URL_HELPERS)
  56. 1 private
  57. 1 def _devise_route_context
  58. @_devise_route_context ||= send(Devise.available_router_name)
  59. end
  60. end
  61. end
  62. end

target/rubygems/gems/devise-4.7.1/lib/devise/delegator.rb

50.0% lines covered

8 relevant lines. 4 lines covered and 4 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Devise
  3. # Checks the scope in the given environment and returns the associated failure app.
  4. 1 class Delegator
  5. 1 def call(env)
  6. failure_app(env).call(env)
  7. end
  8. 1 def failure_app(env)
  9. app = env["warden.options"] &&
  10. (scope = env["warden.options"][:scope]) &&
  11. Devise.mappings[scope.to_sym].failure_app
  12. app || Devise::FailureApp
  13. end
  14. end
  15. end

target/rubygems/gems/devise-4.7.1/lib/devise/failure_app.rb

30.22% lines covered

139 relevant lines. 42 lines covered and 97 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "action_controller/metal"
  3. 1 module Devise
  4. # Failure application that will be called every time :warden is thrown from
  5. # any strategy or hook. It is responsible for redirecting the user to the sign
  6. # in page based on current scope and mapping. If no scope is given, it
  7. # redirects to the default_url.
  8. 1 class FailureApp < ActionController::Metal
  9. 1 include ActionController::UrlFor
  10. 1 include ActionController::Redirecting
  11. 1 include Rails.application.routes.url_helpers
  12. 1 include Rails.application.routes.mounted_helpers
  13. 1 include Devise::Controllers::StoreLocation
  14. 1 delegate :flash, to: :request
  15. 1 def self.call(env)
  16. @respond ||= action(:respond)
  17. @respond.call(env)
  18. end
  19. # Try retrieving the URL options from the parent controller (usually
  20. # ApplicationController). Instance methods are not supported at the moment,
  21. # so only the class-level attribute is used.
  22. 1 def self.default_url_options(*args)
  23. if defined?(Devise.parent_controller.constantize)
  24. Devise.parent_controller.constantize.try(:default_url_options) || {}
  25. else
  26. {}
  27. end
  28. end
  29. 1 def respond
  30. if http_auth?
  31. http_auth
  32. elsif warden_options[:recall]
  33. recall
  34. else
  35. redirect
  36. end
  37. end
  38. 1 def http_auth
  39. self.status = 401
  40. self.headers["WWW-Authenticate"] = %(Basic realm=#{Devise.http_authentication_realm.inspect}) if http_auth_header?
  41. self.content_type = request.format.to_s
  42. self.response_body = http_auth_body
  43. end
  44. 1 def recall
  45. header_info = if relative_url_root?
  46. base_path = Pathname.new(relative_url_root)
  47. full_path = Pathname.new(attempted_path)
  48. { "SCRIPT_NAME" => relative_url_root,
  49. "PATH_INFO" => '/' + full_path.relative_path_from(base_path).to_s }
  50. else
  51. { "PATH_INFO" => attempted_path }
  52. end
  53. header_info.each do | var, value|
  54. if request.respond_to?(:set_header)
  55. request.set_header(var, value)
  56. else
  57. request.env[var] = value
  58. end
  59. end
  60. flash.now[:alert] = i18n_message(:invalid) if is_flashing_format?
  61. # self.response = recall_app(warden_options[:recall]).call(env)
  62. self.response = recall_app(warden_options[:recall]).call(request.env)
  63. end
  64. 1 def redirect
  65. store_location!
  66. if is_flashing_format?
  67. if flash[:timedout] && flash[:alert]
  68. flash.keep(:timedout)
  69. flash.keep(:alert)
  70. else
  71. flash[:alert] = i18n_message
  72. end
  73. end
  74. redirect_to redirect_url
  75. end
  76. 1 protected
  77. 1 def i18n_options(options)
  78. options
  79. end
  80. 1 def i18n_message(default = nil)
  81. message = warden_message || default || :unauthenticated
  82. if message.is_a?(Symbol)
  83. options = {}
  84. options[:resource_name] = scope
  85. options[:scope] = "devise.failure"
  86. options[:default] = [message]
  87. auth_keys = scope_class.authentication_keys
  88. keys = (auth_keys.respond_to?(:keys) ? auth_keys.keys : auth_keys).map { |key| scope_class.human_attribute_name(key) }
  89. options[:authentication_keys] = keys.join(I18n.translate(:"support.array.words_connector"))
  90. options = i18n_options(options)
  91. I18n.t(:"#{scope}.#{message}", options)
  92. else
  93. message.to_s
  94. end
  95. end
  96. 1 def redirect_url
  97. if warden_message == :timeout
  98. flash[:timedout] = true if is_flashing_format?
  99. path = if request.get?
  100. attempted_path
  101. else
  102. request.referrer
  103. end
  104. path || scope_url
  105. else
  106. scope_url
  107. end
  108. end
  109. 1 def route(scope)
  110. :"new_#{scope}_session_url"
  111. end
  112. 1 def scope_url
  113. opts = {}
  114. # Initialize script_name with nil to prevent infinite loops in
  115. # authenticated mounted engines in rails 4.2 and 5.0
  116. opts[:script_name] = nil
  117. route = route(scope)
  118. opts[:format] = request_format unless skip_format?
  119. router_name = Devise.mappings[scope].router_name || Devise.available_router_name
  120. context = send(router_name)
  121. if relative_url_root?
  122. opts[:script_name] = relative_url_root
  123. # We need to add the rootpath to `script_name` manually for applications that use a Rails
  124. # version lower than 5.1. Otherwise, it is going to generate a wrong path for Engines
  125. # that use Devise. Remove it when the support of Rails 5.0 is droped.
  126. elsif root_path_defined?(context) && !rails_51_and_up?
  127. rootpath = context.routes.url_helpers.root_path
  128. opts[:script_name] = rootpath.chomp('/') if rootpath.length > 1
  129. end
  130. if context.respond_to?(route)
  131. context.send(route, opts)
  132. elsif respond_to?(:root_url)
  133. root_url(opts)
  134. else
  135. "/"
  136. end
  137. end
  138. 1 def skip_format?
  139. %w(html */*).include? request_format.to_s
  140. end
  141. # Choose whether we should respond in an HTTP authentication fashion,
  142. # including 401 and optional headers.
  143. #
  144. # This method allows the user to explicitly disable HTTP authentication
  145. # on AJAX requests in case they want to redirect on failures instead of
  146. # handling the errors on their own. This is useful in case your AJAX API
  147. # is the same as your public API and uses a format like JSON (so you
  148. # cannot mark JSON as a navigational format).
  149. 1 def http_auth?
  150. if request.xhr?
  151. Devise.http_authenticatable_on_xhr
  152. else
  153. !(request_format && is_navigational_format?)
  154. end
  155. end
  156. # It doesn't make sense to send authenticate headers in AJAX requests
  157. # or if the user disabled them.
  158. 1 def http_auth_header?
  159. scope_class.http_authenticatable && !request.xhr?
  160. end
  161. 1 def http_auth_body
  162. return i18n_message unless request_format
  163. method = "to_#{request_format}"
  164. if method == "to_xml"
  165. { error: i18n_message }.to_xml(root: "errors")
  166. elsif {}.respond_to?(method)
  167. { error: i18n_message }.send(method)
  168. else
  169. i18n_message
  170. end
  171. end
  172. 1 def recall_app(app)
  173. controller, action = app.split("#")
  174. controller_name = ActiveSupport::Inflector.camelize(controller)
  175. controller_klass = ActiveSupport::Inflector.constantize("#{controller_name}Controller")
  176. controller_klass.action(action)
  177. end
  178. 1 def warden
  179. request.respond_to?(:get_header) ? request.get_header("warden") : request.env["warden"]
  180. end
  181. 1 def warden_options
  182. request.respond_to?(:get_header) ? request.get_header("warden.options") : request.env["warden.options"]
  183. end
  184. 1 def warden_message
  185. @message ||= warden.message || warden_options[:message]
  186. end
  187. 1 def scope
  188. @scope ||= warden_options[:scope] || Devise.default_scope
  189. end
  190. 1 def scope_class
  191. @scope_class ||= Devise.mappings[scope].to
  192. end
  193. 1 def attempted_path
  194. warden_options[:attempted_path]
  195. end
  196. # Stores requested URI to redirect the user after signing in. We can't use
  197. # the scoped session provided by warden here, since the user is not
  198. # authenticated yet, but we still need to store the URI based on scope, so
  199. # different scopes would never use the same URI to redirect.
  200. 1 def store_location!
  201. store_location_for(scope, attempted_path) if request.get? && !http_auth?
  202. end
  203. 1 def is_navigational_format?
  204. Devise.navigational_formats.include?(request_format)
  205. end
  206. # Check if flash messages should be emitted. Default is to do it on
  207. # navigational formats
  208. 1 def is_flashing_format?
  209. request.respond_to?(:flash) && is_navigational_format?
  210. end
  211. 1 def request_format
  212. @request_format ||= request.format.try(:ref)
  213. end
  214. 1 def relative_url_root
  215. @relative_url_root ||= begin
  216. config = Rails.application.config
  217. config.try(:relative_url_root) || config.action_controller.try(:relative_url_root)
  218. end
  219. end
  220. 1 def relative_url_root?
  221. relative_url_root.present?
  222. end
  223. 1 ActiveSupport.run_load_hooks(:devise_failure_app, self)
  224. 1 private
  225. 1 def root_path_defined?(context)
  226. defined?(context.routes) && context.routes.url_helpers.respond_to?(:root_path)
  227. end
  228. 1 def rails_51_and_up?
  229. Rails.gem_version >= Gem::Version.new("5.1")
  230. end
  231. end
  232. end

target/rubygems/gems/devise-4.7.1/lib/devise/hooks/forgetable.rb

33.33% lines covered

3 relevant lines. 1 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. # Before logout hook to forget the user in the given scope, if it responds
  3. # to forget_me! Also clear remember token to ensure the user won't be
  4. # remembered again. Notice that we forget the user unless the record is not persisted.
  5. # This avoids forgetting deleted users.
  6. 1 Warden::Manager.before_logout do |record, warden, options|
  7. if record.respond_to?(:forget_me!)
  8. Devise::Hooks::Proxy.new(warden).forget_me(record)
  9. end
  10. end

target/rubygems/gems/devise-4.7.1/lib/devise/hooks/rememberable.rb

80.0% lines covered

5 relevant lines. 4 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 Warden::Manager.after_set_user except: :fetch do |record, warden, options|
  3. 2 scope = options[:scope]
  4. 2 if record.respond_to?(:remember_me) && options[:store] != false &&
  5. 1 record.remember_me && warden.authenticated?(scope)
  6. Devise::Hooks::Proxy.new(warden).remember_me(record)
  7. end
  8. end

target/rubygems/gems/devise-4.7.1/lib/devise/models/database_authenticatable.rb

41.67% lines covered

84 relevant lines. 35 lines covered and 49 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require 'devise/strategies/database_authenticatable'
  3. 1 module Devise
  4. 1 module Models
  5. # Authenticatable Module, responsible for hashing the password and
  6. # validating the authenticity of a user while signing in.
  7. #
  8. # == Options
  9. #
  10. # DatabaseAuthenticatable adds the following options to devise_for:
  11. #
  12. # * +pepper+: a random string used to provide a more secure hash. Use
  13. # `rails secret` to generate new keys.
  14. #
  15. # * +stretches+: the cost given to bcrypt.
  16. #
  17. # * +send_email_changed_notification+: notify original email when it changes.
  18. #
  19. # * +send_password_change_notification+: notify email when password changes.
  20. #
  21. # == Examples
  22. #
  23. # User.find(1).valid_password?('password123') # returns true/false
  24. #
  25. 1 module DatabaseAuthenticatable
  26. 1 extend ActiveSupport::Concern
  27. 1 included do
  28. 1 after_update :send_email_changed_notification, if: :send_email_changed_notification?
  29. 1 after_update :send_password_change_notification, if: :send_password_change_notification?
  30. 1 attr_reader :password, :current_password
  31. 1 attr_accessor :password_confirmation
  32. end
  33. 1 def initialize(*args, &block)
  34. @skip_email_changed_notification = false
  35. @skip_password_change_notification = false
  36. super
  37. end
  38. # Skips sending the email changed notification after_update
  39. 1 def skip_email_changed_notification!
  40. @skip_email_changed_notification = true
  41. end
  42. # Skips sending the password change notification after_update
  43. 1 def skip_password_change_notification!
  44. @skip_password_change_notification = true
  45. end
  46. 1 def self.required_fields(klass)
  47. [:encrypted_password] + klass.authentication_keys
  48. end
  49. # Generates a hashed password based on the given value.
  50. # For legacy reasons, we use `encrypted_password` to store
  51. # the hashed password.
  52. 1 def password=(new_password)
  53. @password = new_password
  54. self.encrypted_password = password_digest(@password) if @password.present?
  55. end
  56. # Verifies whether a password (ie from sign in) is the user password.
  57. 1 def valid_password?(password)
  58. Devise::Encryptor.compare(self.class, encrypted_password, password)
  59. end
  60. # Set password and password confirmation to nil
  61. 1 def clean_up_passwords
  62. self.password = self.password_confirmation = nil
  63. end
  64. # Update record attributes when :current_password matches, otherwise
  65. # returns error on :current_password.
  66. #
  67. # This method also rejects the password field if it is blank (allowing
  68. # users to change relevant information like the e-mail without changing
  69. # their password). In case the password field is rejected, the confirmation
  70. # is also rejected as long as it is also blank.
  71. 1 def update_with_password(params, *options)
  72. if options.present?
  73. ActiveSupport::Deprecation.warn <<-DEPRECATION.strip_heredoc
  74. [Devise] The second argument of `DatabaseAuthenticatable#update_with_password`
  75. (`options`) is deprecated and it will be removed in the next major version.
  76. It was added to support a feature deprecated in Rails 4, so you can safely remove it
  77. from your code.
  78. DEPRECATION
  79. end
  80. current_password = params.delete(:current_password)
  81. if params[:password].blank?
  82. params.delete(:password)
  83. params.delete(:password_confirmation) if params[:password_confirmation].blank?
  84. end
  85. result = if valid_password?(current_password)
  86. update(params, *options)
  87. else
  88. assign_attributes(params, *options)
  89. valid?
  90. errors.add(:current_password, current_password.blank? ? :blank : :invalid)
  91. false
  92. end
  93. clean_up_passwords
  94. result
  95. end
  96. # Updates record attributes without asking for the current password.
  97. # Never allows a change to the current password. If you are using this
  98. # method, you should probably override this method to protect other
  99. # attributes you would not like to be updated without a password.
  100. #
  101. # Example:
  102. #
  103. # def update_without_password(params, *options)
  104. # params.delete(:email)
  105. # super(params)
  106. # end
  107. #
  108. 1 def update_without_password(params, *options)
  109. if options.present?
  110. ActiveSupport::Deprecation.warn <<-DEPRECATION.strip_heredoc
  111. [Devise] The second argument of `DatabaseAuthenticatable#update_without_password`
  112. (`options`) is deprecated and it will be removed in the next major version.
  113. It was added to support a feature deprecated in Rails 4, so you can safely remove it
  114. from your code.
  115. DEPRECATION
  116. end
  117. params.delete(:password)
  118. params.delete(:password_confirmation)
  119. result = update(params, *options)
  120. clean_up_passwords
  121. result
  122. end
  123. # Destroy record when :current_password matches, otherwise returns
  124. # error on :current_password. It also automatically rejects
  125. # :current_password if it is blank.
  126. 1 def destroy_with_password(current_password)
  127. result = if valid_password?(current_password)
  128. destroy
  129. else
  130. valid?
  131. errors.add(:current_password, current_password.blank? ? :blank : :invalid)
  132. false
  133. end
  134. result
  135. end
  136. # A callback initiated after successfully authenticating. This can be
  137. # used to insert your own logic that is only run after the user successfully
  138. # authenticates.
  139. #
  140. # Example:
  141. #
  142. # def after_database_authentication
  143. # self.update_attribute(:invite_code, nil)
  144. # end
  145. #
  146. 1 def after_database_authentication
  147. end
  148. # A reliable way to expose the salt regardless of the implementation.
  149. 1 def authenticatable_salt
  150. 2 encrypted_password[0,29] if encrypted_password
  151. end
  152. 1 if Devise.activerecord51?
  153. # Send notification to user when email changes.
  154. 1 def send_email_changed_notification
  155. send_devise_notification(:email_changed, to: email_before_last_save)
  156. end
  157. else
  158. # Send notification to user when email changes.
  159. def send_email_changed_notification
  160. send_devise_notification(:email_changed, to: email_was)
  161. end
  162. end
  163. # Send notification to user when password changes.
  164. 1 def send_password_change_notification
  165. send_devise_notification(:password_change)
  166. end
  167. 1 protected
  168. # Hashes the password using bcrypt. Custom hash functions should override
  169. # this method to apply their own algorithm.
  170. #
  171. # See https://github.com/plataformatec/devise-encryptable for examples
  172. # of other hashing engines.
  173. 1 def password_digest(password)
  174. Devise::Encryptor.digest(self.class, password)
  175. end
  176. 1 if Devise.activerecord51?
  177. 1 def send_email_changed_notification?
  178. self.class.send_email_changed_notification && saved_change_to_email? && !@skip_email_changed_notification
  179. end
  180. else
  181. def send_email_changed_notification?
  182. self.class.send_email_changed_notification && email_changed? && !@skip_email_changed_notification
  183. end
  184. end
  185. 1 if Devise.activerecord51?
  186. 1 def send_password_change_notification?
  187. self.class.send_password_change_notification && saved_change_to_encrypted_password? && !@skip_password_change_notification
  188. end
  189. else
  190. def send_password_change_notification?
  191. self.class.send_password_change_notification && encrypted_password_changed? && !@skip_password_change_notification
  192. end
  193. end
  194. 1 module ClassMethods
  195. 1 Devise::Models.config(self, :pepper, :stretches, :send_email_changed_notification, :send_password_change_notification)
  196. # We assume this method already gets the sanitized values from the
  197. # DatabaseAuthenticatable strategy. If you are using this method on
  198. # your own, be sure to sanitize the conditions hash to only include
  199. # the proper fields.
  200. 1 def find_for_database_authentication(conditions)
  201. find_for_authentication(conditions)
  202. end
  203. end
  204. end
  205. end
  206. end

target/rubygems/gems/devise-4.7.1/lib/devise/models/recoverable.rb

33.33% lines covered

63 relevant lines. 21 lines covered and 42 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Devise
  3. 1 module Models
  4. # Recoverable takes care of resetting the user password and send reset instructions.
  5. #
  6. # ==Options
  7. #
  8. # Recoverable adds the following options to devise_for:
  9. #
  10. # * +reset_password_keys+: the keys you want to use when recovering the password for an account
  11. # * +reset_password_within+: the time period within which the password must be reset or the token expires.
  12. # * +sign_in_after_reset_password+: whether or not to sign in the user automatically after a password reset.
  13. #
  14. # == Examples
  15. #
  16. # # resets the user password and save the record, true if valid passwords are given, otherwise false
  17. # User.find(1).reset_password('password123', 'password123')
  18. #
  19. # # creates a new token and send it with instructions about how to reset the password
  20. # User.find(1).send_reset_password_instructions
  21. #
  22. 1 module Recoverable
  23. 1 extend ActiveSupport::Concern
  24. 1 def self.required_fields(klass)
  25. [:reset_password_sent_at, :reset_password_token]
  26. end
  27. 1 included do
  28. 1 before_update :clear_reset_password_token, if: :clear_reset_password_token?
  29. end
  30. # Update password saving the record and clearing token. Returns true if
  31. # the passwords are valid and the record was saved, false otherwise.
  32. 1 def reset_password(new_password, new_password_confirmation)
  33. if new_password.present?
  34. self.password = new_password
  35. self.password_confirmation = new_password_confirmation
  36. save
  37. else
  38. errors.add(:password, :blank)
  39. false
  40. end
  41. end
  42. # Resets reset password token and send reset password instructions by email.
  43. # Returns the token sent in the e-mail.
  44. 1 def send_reset_password_instructions
  45. token = set_reset_password_token
  46. send_reset_password_instructions_notification(token)
  47. token
  48. end
  49. # Checks if the reset password token sent is within the limit time.
  50. # We do this by calculating if the difference between today and the
  51. # sending date does not exceed the confirm in time configured.
  52. # Returns true if the resource is not responding to reset_password_sent_at at all.
  53. # reset_password_within is a model configuration, must always be an integer value.
  54. #
  55. # Example:
  56. #
  57. # # reset_password_within = 1.day and reset_password_sent_at = today
  58. # reset_password_period_valid? # returns true
  59. #
  60. # # reset_password_within = 5.days and reset_password_sent_at = 4.days.ago
  61. # reset_password_period_valid? # returns true
  62. #
  63. # # reset_password_within = 5.days and reset_password_sent_at = 5.days.ago
  64. # reset_password_period_valid? # returns false
  65. #
  66. # # reset_password_within = 0.days
  67. # reset_password_period_valid? # will always return false
  68. #
  69. 1 def reset_password_period_valid?
  70. reset_password_sent_at && reset_password_sent_at.utc >= self.class.reset_password_within.ago.utc
  71. end
  72. 1 protected
  73. # Removes reset_password token
  74. 1 def clear_reset_password_token
  75. self.reset_password_token = nil
  76. self.reset_password_sent_at = nil
  77. end
  78. 1 def set_reset_password_token
  79. raw, enc = Devise.token_generator.generate(self.class, :reset_password_token)
  80. self.reset_password_token = enc
  81. self.reset_password_sent_at = Time.now.utc
  82. save(validate: false)
  83. raw
  84. end
  85. 1 def send_reset_password_instructions_notification(token)
  86. send_devise_notification(:reset_password_instructions, token, {})
  87. end
  88. 1 if Devise.activerecord51?
  89. 1 def clear_reset_password_token?
  90. encrypted_password_changed = respond_to?(:will_save_change_to_encrypted_password?) && will_save_change_to_encrypted_password?
  91. authentication_keys_changed = self.class.authentication_keys.any? do |attribute|
  92. respond_to?("will_save_change_to_#{attribute}?") && send("will_save_change_to_#{attribute}?")
  93. end
  94. authentication_keys_changed || encrypted_password_changed
  95. end
  96. else
  97. def clear_reset_password_token?
  98. encrypted_password_changed = respond_to?(:encrypted_password_changed?) && encrypted_password_changed?
  99. authentication_keys_changed = self.class.authentication_keys.any? do |attribute|
  100. respond_to?("#{attribute}_changed?") && send("#{attribute}_changed?")
  101. end
  102. authentication_keys_changed || encrypted_password_changed
  103. end
  104. end
  105. 1 module ClassMethods
  106. # Attempt to find a user by password reset token. If a user is found, return it
  107. # If a user is not found, return nil
  108. 1 def with_reset_password_token(token)
  109. reset_password_token = Devise.token_generator.digest(self, :reset_password_token, token)
  110. to_adapter.find_first(reset_password_token: reset_password_token)
  111. end
  112. # Attempt to find a user by its email. If a record is found, send new
  113. # password instructions to it. If user is not found, returns a new user
  114. # with an email not found error.
  115. # Attributes must contain the user's email
  116. 1 def send_reset_password_instructions(attributes={})
  117. recoverable = find_or_initialize_with_errors(reset_password_keys, attributes, :not_found)
  118. recoverable.send_reset_password_instructions if recoverable.persisted?
  119. recoverable
  120. end
  121. # Attempt to find a user by its reset_password_token to reset its
  122. # password. If a user is found and token is still valid, reset its password and automatically
  123. # try saving the record. If not user is found, returns a new user
  124. # containing an error in reset_password_token attribute.
  125. # Attributes must contain reset_password_token, password and confirmation
  126. 1 def reset_password_by_token(attributes={})
  127. original_token = attributes[:reset_password_token]
  128. reset_password_token = Devise.token_generator.digest(self, :reset_password_token, original_token)
  129. recoverable = find_or_initialize_with_error_by(:reset_password_token, reset_password_token)
  130. if recoverable.persisted?
  131. if recoverable.reset_password_period_valid?
  132. recoverable.reset_password(attributes[:password], attributes[:password_confirmation])
  133. else
  134. recoverable.errors.add(:reset_password_token, :expired)
  135. end
  136. end
  137. recoverable.reset_password_token = original_token if recoverable.reset_password_token.present?
  138. recoverable
  139. end
  140. 1 Devise::Models.config(self, :reset_password_keys, :reset_password_within, :sign_in_after_reset_password)
  141. end
  142. end
  143. end
  144. end

target/rubygems/gems/devise-4.7.1/lib/devise/models/registerable.rb

80.0% lines covered

10 relevant lines. 8 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Devise
  3. 1 module Models
  4. # Registerable is responsible for everything related to registering a new
  5. # resource (ie user sign up).
  6. 1 module Registerable
  7. 1 extend ActiveSupport::Concern
  8. 1 def self.required_fields(klass)
  9. []
  10. end
  11. 1 module ClassMethods
  12. # A convenience method that receives both parameters and session to
  13. # initialize a user. This can be used by OAuth, for example, to send
  14. # in the user token and be stored on initialization.
  15. #
  16. # By default discards all information sent by the session by calling
  17. # new with params.
  18. 1 def new_with_session(params, session)
  19. new(params)
  20. end
  21. 1 Devise::Models.config(self, :sign_in_after_change_password)
  22. end
  23. end
  24. end
  25. end

target/rubygems/gems/devise-4.7.1/lib/devise/models/rememberable.rb

43.64% lines covered

55 relevant lines. 24 lines covered and 31 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require 'devise/strategies/rememberable'
  3. 1 require 'devise/hooks/rememberable'
  4. 1 require 'devise/hooks/forgetable'
  5. 1 module Devise
  6. 1 module Models
  7. # Rememberable manages generating and clearing token for remembering the user
  8. # from a saved cookie. Rememberable also has utility methods for dealing
  9. # with serializing the user into the cookie and back from the cookie, trying
  10. # to lookup the record based on the saved information.
  11. # You probably wouldn't use rememberable methods directly, they are used
  12. # mostly internally for handling the remember token.
  13. #
  14. # == Options
  15. #
  16. # Rememberable adds the following options in devise_for:
  17. #
  18. # * +remember_for+: the time you want the user will be remembered without
  19. # asking for credentials. After this time the user will be blocked and
  20. # will have to enter their credentials again. This configuration is also
  21. # used to calculate the expires time for the cookie created to remember
  22. # the user. By default remember_for is 2.weeks.
  23. #
  24. # * +extend_remember_period+: if true, extends the user's remember period
  25. # when remembered via cookie. False by default.
  26. #
  27. # * +rememberable_options+: configuration options passed to the created cookie.
  28. #
  29. # == Examples
  30. #
  31. # User.find(1).remember_me! # regenerating the token
  32. # User.find(1).forget_me! # clearing the token
  33. #
  34. # # generating info to put into cookies
  35. # User.serialize_into_cookie(user)
  36. #
  37. # # lookup the user based on the incoming cookie information
  38. # User.serialize_from_cookie(cookie_string)
  39. 1 module Rememberable
  40. 1 extend ActiveSupport::Concern
  41. 1 attr_accessor :remember_me
  42. 1 def self.required_fields(klass)
  43. [:remember_created_at]
  44. end
  45. 1 def remember_me!
  46. self.remember_token ||= self.class.remember_token if respond_to?(:remember_token)
  47. self.remember_created_at ||= Time.now.utc
  48. save(validate: false) if self.changed?
  49. end
  50. # If the record is persisted, remove the remember token (but only if
  51. # it exists), and save the record without validations.
  52. 1 def forget_me!
  53. return unless persisted?
  54. self.remember_token = nil if respond_to?(:remember_token)
  55. self.remember_created_at = nil if self.class.expire_all_remember_me_on_sign_out
  56. save(validate: false)
  57. end
  58. 1 def remember_expires_at
  59. self.class.remember_for.from_now
  60. end
  61. 1 def extend_remember_period
  62. self.class.extend_remember_period
  63. end
  64. 1 def rememberable_value
  65. if respond_to?(:remember_token)
  66. remember_token
  67. elsif respond_to?(:authenticatable_salt) && (salt = authenticatable_salt.presence)
  68. salt
  69. else
  70. raise "authenticatable_salt returned nil for the #{self.class.name} model. " \
  71. "In order to use rememberable, you must ensure a password is always set " \
  72. "or have a remember_token column in your model or implement your own " \
  73. "rememberable_value in the model with custom logic."
  74. end
  75. end
  76. 1 def rememberable_options
  77. self.class.rememberable_options
  78. end
  79. # A callback initiated after successfully being remembered. This can be
  80. # used to insert your own logic that is only run after the user is
  81. # remembered.
  82. #
  83. # Example:
  84. #
  85. # def after_remembered
  86. # self.update_attribute(:invite_code, nil)
  87. # end
  88. #
  89. 1 def after_remembered
  90. end
  91. 1 def remember_me?(token, generated_at)
  92. # TODO: Normalize the JSON type coercion along with the Timeoutable hook
  93. # in a single place https://github.com/plataformatec/devise/blob/ffe9d6d406e79108cf32a2c6a1d0b3828849c40b/lib/devise/hooks/timeoutable.rb#L14-L18
  94. if generated_at.is_a?(String)
  95. generated_at = time_from_json(generated_at)
  96. end
  97. # The token is only valid if:
  98. # 1. we have a date
  99. # 2. the current time does not pass the expiry period
  100. # 3. the record has a remember_created_at date
  101. # 4. the token date is bigger than the remember_created_at
  102. # 5. the token matches
  103. generated_at.is_a?(Time) &&
  104. (self.class.remember_for.ago < generated_at) &&
  105. (generated_at > (remember_created_at || Time.now).utc) &&
  106. Devise.secure_compare(rememberable_value, token)
  107. end
  108. 1 private
  109. 1 def time_from_json(value)
  110. if value =~ /\A\d+\.\d+\Z/
  111. Time.at(value.to_f)
  112. else
  113. Time.parse(value) rescue nil
  114. end
  115. end
  116. 1 module ClassMethods
  117. # Create the cookie key using the record id and remember_token
  118. 1 def serialize_into_cookie(record)
  119. [record.to_key, record.rememberable_value, Time.now.utc.to_f.to_s]
  120. end
  121. # Recreate the user based on the stored cookie
  122. 1 def serialize_from_cookie(*args)
  123. id, token, generated_at = *args
  124. record = to_adapter.get(id)
  125. record if record && record.remember_me?(token, generated_at)
  126. end
  127. # Generate a token checking if one does not already exist in the database.
  128. 1 def remember_token #:nodoc:
  129. loop do
  130. token = Devise.friendly_token
  131. break token unless to_adapter.find_first({ remember_token: token })
  132. end
  133. end
  134. 1 Devise::Models.config(self, :remember_for, :extend_remember_period, :rememberable_options, :expire_all_remember_me_on_sign_out)
  135. end
  136. end
  137. end
  138. end

target/rubygems/gems/devise-4.7.1/lib/devise/models/validatable.rb

77.42% lines covered

31 relevant lines. 24 lines covered and 7 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Devise
  3. 1 module Models
  4. # Validatable creates all needed validations for a user email and password.
  5. # It's optional, given you may want to create the validations by yourself.
  6. # Automatically validate if the email is present, unique and its format is
  7. # valid. Also tests presence of password, confirmation and length.
  8. #
  9. # == Options
  10. #
  11. # Validatable adds the following options to devise_for:
  12. #
  13. # * +email_regexp+: the regular expression used to validate e-mails;
  14. # * +password_length+: a range expressing password length. Defaults to 6..128.
  15. #
  16. 1 module Validatable
  17. # All validations used by this module.
  18. 1 VALIDATIONS = [:validates_presence_of, :validates_uniqueness_of, :validates_format_of,
  19. :validates_confirmation_of, :validates_length_of].freeze
  20. 1 def self.required_fields(klass)
  21. []
  22. end
  23. 1 def self.included(base)
  24. 1 base.extend ClassMethods
  25. 1 assert_validations_api!(base)
  26. 1 base.class_eval do
  27. 1 validates_presence_of :email, if: :email_required?
  28. 1 if Devise.activerecord51?
  29. 1 validates_uniqueness_of :email, allow_blank: true, case_sensitive: true, if: :will_save_change_to_email?
  30. 1 validates_format_of :email, with: email_regexp, allow_blank: true, if: :will_save_change_to_email?
  31. else
  32. validates_uniqueness_of :email, allow_blank: true, if: :email_changed?
  33. validates_format_of :email, with: email_regexp, allow_blank: true, if: :email_changed?
  34. end
  35. 1 validates_presence_of :password, if: :password_required?
  36. 1 validates_confirmation_of :password, if: :password_required?
  37. 1 validates_length_of :password, within: password_length, allow_blank: true
  38. end
  39. end
  40. 1 def self.assert_validations_api!(base) #:nodoc:
  41. 6 unavailable_validations = VALIDATIONS.select { |v| !base.respond_to?(v) }
  42. 1 unless unavailable_validations.empty?
  43. raise "Could not use :validatable module since #{base} does not respond " <<
  44. "to the following methods: #{unavailable_validations.to_sentence}."
  45. end
  46. end
  47. 1 protected
  48. # Checks whether a password is needed or not. For validations only.
  49. # Passwords are always required if it's a new record, or if the password
  50. # or confirmation are being set somewhere.
  51. 1 def password_required?
  52. !persisted? || !password.nil? || !password_confirmation.nil?
  53. end
  54. 1 def email_required?
  55. true
  56. end
  57. 1 module ClassMethods
  58. 1 Devise::Models.config(self, :email_regexp, :password_length)
  59. end
  60. end
  61. end
  62. end

target/rubygems/gems/devise-4.7.1/lib/devise/orm/active_record.rb

100.0% lines covered

3 relevant lines. 3 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require 'orm_adapter/adapters/active_record'
  3. 1 ActiveSupport.on_load(:active_record) do
  4. 1 extend Devise::Models
  5. end

target/rubygems/gems/devise-4.7.1/lib/devise/secret_key_finder.rb

76.92% lines covered

13 relevant lines. 10 lines covered and 3 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Devise
  3. 1 class SecretKeyFinder
  4. 1 def initialize(application)
  5. 1 @application = application
  6. end
  7. 1 def find
  8. 1 if @application.respond_to?(:credentials) && key_exists?(@application.credentials)
  9. 1 @application.credentials.secret_key_base
  10. elsif @application.respond_to?(:secrets) && key_exists?(@application.secrets)
  11. @application.secrets.secret_key_base
  12. elsif @application.config.respond_to?(:secret_key_base) && key_exists?(@application.config)
  13. @application.config.secret_key_base
  14. elsif @application.respond_to?(:secret_key_base) && key_exists?(@application)
  15. @application.secret_key_base
  16. end
  17. end
  18. 1 private
  19. 1 def key_exists?(object)
  20. 1 object.secret_key_base.present?
  21. end
  22. end
  23. end

target/rubygems/gems/devise-4.7.1/lib/devise/strategies/authenticatable.rb

41.43% lines covered

70 relevant lines. 29 lines covered and 41 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require 'devise/strategies/base'
  3. 1 module Devise
  4. 1 module Strategies
  5. # This strategy should be used as basis for authentication strategies. It retrieves
  6. # parameters both from params or from http authorization headers. See database_authenticatable
  7. # for an example.
  8. 1 class Authenticatable < Base
  9. 1 attr_accessor :authentication_hash, :authentication_type, :password
  10. 1 def store?
  11. super && !mapping.to.skip_session_storage.include?(authentication_type)
  12. end
  13. 1 def valid?
  14. valid_for_params_auth? || valid_for_http_auth?
  15. end
  16. # Override and set to false for things like OmniAuth that technically
  17. # run through Authentication (user_set) very often, which would normally
  18. # reset CSRF data in the session
  19. 1 def clean_up_csrf?
  20. true
  21. end
  22. 1 private
  23. # Receives a resource and check if it is valid by calling valid_for_authentication?
  24. # A block that will be triggered while validating can be optionally
  25. # given as parameter. Check Devise::Models::Authenticatable.valid_for_authentication?
  26. # for more information.
  27. #
  28. # In case the resource can't be validated, it will fail with the given
  29. # unauthenticated_message.
  30. 1 def validate(resource, &block)
  31. result = resource && resource.valid_for_authentication?(&block)
  32. if result
  33. true
  34. else
  35. if resource
  36. fail!(resource.unauthenticated_message)
  37. end
  38. false
  39. end
  40. end
  41. # Get values from params and set in the resource.
  42. 1 def remember_me(resource)
  43. resource.remember_me = remember_me? if resource.respond_to?(:remember_me=)
  44. end
  45. # Should this resource be marked to be remembered?
  46. 1 def remember_me?
  47. valid_params? && Devise::TRUE_VALUES.include?(params_auth_hash[:remember_me])
  48. end
  49. # Check if this is a valid strategy for http authentication by:
  50. #
  51. # * Validating if the model allows http authentication;
  52. # * If any of the authorization headers were sent;
  53. # * If all authentication keys are present;
  54. #
  55. 1 def valid_for_http_auth?
  56. http_authenticatable? && request.authorization && with_authentication_hash(:http_auth, http_auth_hash)
  57. end
  58. # Check if this is a valid strategy for params authentication by:
  59. #
  60. # * Validating if the model allows params authentication;
  61. # * If the request hits the sessions controller through POST;
  62. # * If the params[scope] returns a hash with credentials;
  63. # * If all authentication keys are present;
  64. #
  65. 1 def valid_for_params_auth?
  66. params_authenticatable? && valid_params_request? &&
  67. valid_params? && with_authentication_hash(:params_auth, params_auth_hash)
  68. end
  69. # Check if the model accepts this strategy as http authenticatable.
  70. 1 def http_authenticatable?
  71. mapping.to.http_authenticatable?(authenticatable_name)
  72. end
  73. # Check if the model accepts this strategy as params authenticatable.
  74. 1 def params_authenticatable?
  75. mapping.to.params_authenticatable?(authenticatable_name)
  76. end
  77. # Extract the appropriate subhash for authentication from params.
  78. 1 def params_auth_hash
  79. params[scope]
  80. end
  81. # Extract a hash with attributes:values from the http params.
  82. 1 def http_auth_hash
  83. keys = [http_authentication_key, :password]
  84. Hash[*keys.zip(decode_credentials).flatten]
  85. end
  86. # By default, a request is valid if the controller set the proper env variable.
  87. 1 def valid_params_request?
  88. !!env["devise.allow_params_authentication"]
  89. end
  90. # If the request is valid, finally check if params_auth_hash returns a hash.
  91. 1 def valid_params?
  92. params_auth_hash.is_a?(Hash)
  93. end
  94. # Note: unlike `Model.valid_password?`, this method does not actually
  95. # ensure that the password in the params matches the password stored in
  96. # the database. It only checks if the password is *present*. Do not rely
  97. # on this method for validating that a given password is correct.
  98. 1 def valid_password?
  99. password.present?
  100. end
  101. # Helper to decode credentials from HTTP.
  102. 1 def decode_credentials
  103. return [] unless request.authorization && request.authorization =~ /^Basic (.*)/mi
  104. Base64.decode64($1).split(/:/, 2)
  105. end
  106. # Sets the authentication hash and the password from params_auth_hash or http_auth_hash.
  107. 1 def with_authentication_hash(auth_type, auth_values)
  108. self.authentication_hash, self.authentication_type = {}, auth_type
  109. self.password = auth_values[:password]
  110. parse_authentication_key_values(auth_values, authentication_keys) &&
  111. parse_authentication_key_values(request_values, request_keys)
  112. end
  113. 1 def authentication_keys
  114. @authentication_keys ||= mapping.to.authentication_keys
  115. end
  116. 1 def http_authentication_key
  117. @http_authentication_key ||= mapping.to.http_authentication_key || case authentication_keys
  118. when Array then authentication_keys.first
  119. when Hash then authentication_keys.keys.first
  120. end
  121. end
  122. 1 def request_keys
  123. @request_keys ||= mapping.to.request_keys
  124. end
  125. 1 def request_values
  126. keys = request_keys.respond_to?(:keys) ? request_keys.keys : request_keys
  127. values = keys.map { |k| self.request.send(k) }
  128. Hash[keys.zip(values)]
  129. end
  130. 1 def parse_authentication_key_values(hash, keys)
  131. keys.each do |key, enforce|
  132. value = hash[key].presence
  133. if value
  134. self.authentication_hash[key] = value
  135. else
  136. return false unless enforce == false
  137. end
  138. end
  139. true
  140. end
  141. # Holds the authenticatable name for this class. Devise::Strategies::DatabaseAuthenticatable
  142. # becomes simply :database.
  143. 1 def authenticatable_name
  144. @authenticatable_name ||=
  145. ActiveSupport::Inflector.underscore(self.class.name.split("::").last).
  146. sub("_authenticatable", "").to_sym
  147. end
  148. end
  149. end
  150. end

target/rubygems/gems/devise-4.7.1/lib/devise/strategies/base.rb

50.0% lines covered

10 relevant lines. 5 lines covered and 5 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Devise
  3. 1 module Strategies
  4. # Base strategy for Devise. Responsible for verifying correct scope and mapping.
  5. 1 class Base < ::Warden::Strategies::Base
  6. # Whenever CSRF cannot be verified, we turn off any kind of storage
  7. 1 def store?
  8. !env["devise.skip_storage"]
  9. end
  10. # Checks if a valid scope was given for devise and find mapping based on this scope.
  11. 1 def mapping
  12. @mapping ||= begin
  13. mapping = Devise.mappings[scope]
  14. raise "Could not find mapping for #{scope}" unless mapping
  15. mapping
  16. end
  17. end
  18. end
  19. end
  20. end

target/rubygems/gems/devise-4.7.1/lib/devise/strategies/database_authenticatable.rb

40.0% lines covered

15 relevant lines. 6 lines covered and 9 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require 'devise/strategies/authenticatable'
  3. 1 module Devise
  4. 1 module Strategies
  5. # Default strategy for signing in a user, based on their email and password in the database.
  6. 1 class DatabaseAuthenticatable < Authenticatable
  7. 1 def authenticate!
  8. resource = password.present? && mapping.to.find_for_database_authentication(authentication_hash)
  9. hashed = false
  10. if validate(resource){ hashed = true; resource.valid_password?(password) }
  11. remember_me(resource)
  12. resource.after_database_authentication
  13. success!(resource)
  14. end
  15. # In paranoid mode, hash the password even when a resource doesn't exist for the given authentication key.
  16. # This is necessary to prevent enumeration attacks - e.g. the request is faster when a resource doesn't
  17. # exist in the database if the password hashing algorithm is not called.
  18. mapping.to.new.password = password if !hashed && Devise.paranoid
  19. unless resource
  20. Devise.paranoid ? fail(:invalid) : fail(:not_found_in_database)
  21. end
  22. end
  23. end
  24. end
  25. end
  26. 1 Warden::Strategies.add(:database_authenticatable, Devise::Strategies::DatabaseAuthenticatable)

target/rubygems/gems/devise-4.7.1/lib/devise/strategies/rememberable.rb

46.43% lines covered

28 relevant lines. 13 lines covered and 15 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require 'devise/strategies/authenticatable'
  3. 1 module Devise
  4. 1 module Strategies
  5. # Remember the user through the remember token. This strategy is responsible
  6. # to verify whether there is a cookie with the remember token, and to
  7. # recreate the user from this cookie if it exists. Must be called *before*
  8. # authenticatable.
  9. 1 class Rememberable < Authenticatable
  10. # A valid strategy for rememberable needs a remember token in the cookies.
  11. 1 def valid?
  12. @remember_cookie = nil
  13. remember_cookie.present?
  14. end
  15. # To authenticate a user we deserialize the cookie and attempt finding
  16. # the record in the database. If the attempt fails, we pass to another
  17. # strategy handle the authentication.
  18. 1 def authenticate!
  19. resource = mapping.to.serialize_from_cookie(*remember_cookie)
  20. unless resource
  21. cookies.delete(remember_key)
  22. return pass
  23. end
  24. if validate(resource)
  25. remember_me(resource) if extend_remember_me?(resource)
  26. resource.after_remembered
  27. success!(resource)
  28. end
  29. end
  30. # No need to clean up the CSRF when using rememberable.
  31. # In fact, cleaning it up here would be a bug because
  32. # rememberable is triggered on GET requests which means
  33. # we would render a page on first access with all csrf
  34. # tokens expired.
  35. 1 def clean_up_csrf?
  36. false
  37. end
  38. 1 private
  39. 1 def extend_remember_me?(resource)
  40. resource.respond_to?(:extend_remember_period) && resource.extend_remember_period
  41. end
  42. 1 def remember_me?
  43. true
  44. end
  45. 1 def remember_key
  46. mapping.to.rememberable_options.fetch(:key, "remember_#{scope}_token")
  47. end
  48. 1 def remember_cookie
  49. @remember_cookie ||= cookies.signed[remember_key]
  50. end
  51. end
  52. end
  53. end
  54. 1 Warden::Strategies.add(:rememberable, Devise::Strategies::Rememberable)

target/rubygems/gems/devise-4.7.1/lib/devise/token_generator.rb

58.82% lines covered

17 relevant lines. 10 lines covered and 7 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require 'openssl'
  3. 1 module Devise
  4. 1 class TokenGenerator
  5. 1 def initialize(key_generator, digest = "SHA256")
  6. 1 @key_generator = key_generator
  7. 1 @digest = digest
  8. end
  9. 1 def digest(klass, column, value)
  10. value.present? && OpenSSL::HMAC.hexdigest(@digest, key_for(column), value.to_s)
  11. end
  12. 1 def generate(klass, column)
  13. key = key_for(column)
  14. loop do
  15. raw = Devise.friendly_token
  16. enc = OpenSSL::HMAC.hexdigest(@digest, key, raw)
  17. break [raw, enc] unless klass.to_adapter.find_first({ column => enc })
  18. end
  19. end
  20. 1 private
  21. 1 def key_for(column)
  22. @key_generator.generate_key("Devise #{column}")
  23. end
  24. end
  25. end

target/rubygems/gems/globalid-0.4.2/lib/global_id/identification.rb

71.43% lines covered

14 relevant lines. 10 lines covered and 4 lines missed.
    
  1. 1 require 'active_support/concern'
  2. 1 class GlobalID
  3. 1 module Identification
  4. 1 extend ActiveSupport::Concern
  5. 1 def to_global_id(options = {})
  6. GlobalID.create(self, options)
  7. end
  8. 1 alias to_gid to_global_id
  9. 1 def to_gid_param(options = {})
  10. to_global_id(options).to_param
  11. end
  12. 1 def to_signed_global_id(options = {})
  13. SignedGlobalID.create(self, options)
  14. end
  15. 1 alias to_sgid to_signed_global_id
  16. 1 def to_sgid_param(options = {})
  17. to_signed_global_id(options).to_param
  18. end
  19. end
  20. end

target/rubygems/gems/globalid-0.4.2/lib/global_id/signed_global_id.rb

53.33% lines covered

45 relevant lines. 24 lines covered and 21 lines missed.
    
  1. 1 require 'global_id'
  2. 1 require 'active_support/message_verifier'
  3. 1 require 'time'
  4. 1 class SignedGlobalID < GlobalID
  5. 1 class ExpiredMessage < StandardError; end
  6. 1 class << self
  7. 1 attr_accessor :verifier
  8. 1 def parse(sgid, options = {})
  9. super verify(sgid.to_s, options), options
  10. end
  11. # Grab the verifier from options and fall back to SignedGlobalID.verifier.
  12. # Raise ArgumentError if neither is available.
  13. 1 def pick_verifier(options)
  14. options.fetch :verifier do
  15. verifier || raise(ArgumentError, 'Pass a `verifier:` option with an `ActiveSupport::MessageVerifier` instance, or set a default SignedGlobalID.verifier.')
  16. end
  17. end
  18. 1 attr_accessor :expires_in
  19. 1 DEFAULT_PURPOSE = "default"
  20. 1 def pick_purpose(options)
  21. options.fetch :for, DEFAULT_PURPOSE
  22. end
  23. 1 private
  24. 1 def verify(sgid, options)
  25. metadata = pick_verifier(options).verify(sgid)
  26. raise_if_expired(metadata['expires_at'])
  27. metadata['gid'] if pick_purpose(options) == metadata['purpose']
  28. rescue ActiveSupport::MessageVerifier::InvalidSignature, ExpiredMessage
  29. nil
  30. end
  31. 1 def raise_if_expired(expires_at)
  32. if expires_at && Time.now.utc > Time.iso8601(expires_at)
  33. raise ExpiredMessage, 'This signed global id has expired.'
  34. end
  35. end
  36. end
  37. 1 attr_reader :verifier, :purpose, :expires_at
  38. 1 def initialize(gid, options = {})
  39. super
  40. @verifier = self.class.pick_verifier(options)
  41. @purpose = self.class.pick_purpose(options)
  42. @expires_at = pick_expiration(options)
  43. end
  44. 1 def to_s
  45. @sgid ||= @verifier.generate(to_h)
  46. end
  47. 1 alias to_param to_s
  48. 1 def to_h
  49. # Some serializers decodes symbol keys to symbols, others to strings.
  50. # Using string keys remedies that.
  51. { 'gid' => @uri.to_s, 'purpose' => purpose, 'expires_at' => encoded_expiration }
  52. end
  53. 1 def ==(other)
  54. super && @purpose == other.purpose
  55. end
  56. 1 private
  57. 1 def encoded_expiration
  58. expires_at.utc.iso8601(3) if expires_at
  59. end
  60. 1 def pick_expiration(options)
  61. return options[:expires_at] if options.key?(:expires_at)
  62. if expires_in = options.fetch(:expires_in) { self.class.expires_in }
  63. expires_in.from_now
  64. end
  65. end
  66. end

target/rubygems/gems/globalid-0.4.2/lib/global_id/verifier.rb

77.78% lines covered

9 relevant lines. 7 lines covered and 2 lines missed.
    
  1. 1 require 'active_support'
  2. 1 require 'active_support/message_verifier'
  3. 1 class GlobalID
  4. 1 class Verifier < ActiveSupport::MessageVerifier
  5. 1 private
  6. 1 def encode(data)
  7. ::Base64.urlsafe_encode64(data)
  8. end
  9. 1 def decode(data)
  10. ::Base64.urlsafe_decode64(data)
  11. end
  12. end
  13. end

target/rubygems/gems/i18n-1.6.0/lib/i18n/backend.rb

100.0% lines covered

17 relevant lines. 17 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module I18n
  3. 1 module Backend
  4. 1 autoload :Base, 'i18n/backend/base'
  5. 1 autoload :InterpolationCompiler, 'i18n/backend/interpolation_compiler'
  6. 1 autoload :Cache, 'i18n/backend/cache'
  7. 1 autoload :CacheFile, 'i18n/backend/cache_file'
  8. 1 autoload :Cascade, 'i18n/backend/cascade'
  9. 1 autoload :Chain, 'i18n/backend/chain'
  10. 1 autoload :Fallbacks, 'i18n/backend/fallbacks'
  11. 1 autoload :Flatten, 'i18n/backend/flatten'
  12. 1 autoload :Gettext, 'i18n/backend/gettext'
  13. 1 autoload :KeyValue, 'i18n/backend/key_value'
  14. 1 autoload :Memoize, 'i18n/backend/memoize'
  15. 1 autoload :Metadata, 'i18n/backend/metadata'
  16. 1 autoload :Pluralization, 'i18n/backend/pluralization'
  17. 1 autoload :Simple, 'i18n/backend/simple'
  18. 1 autoload :Transliterator, 'i18n/backend/transliterator'
  19. end
  20. end

target/rubygems/gems/i18n-1.6.0/lib/i18n/backend/base.rb

25.56% lines covered

133 relevant lines. 34 lines covered and 99 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require 'yaml'
  3. 1 require 'json'
  4. 1 require 'i18n/core_ext/hash'
  5. 1 module I18n
  6. 1 module Backend
  7. 1 module Base
  8. 1 using I18n::HashRefinements
  9. 1 include I18n::Backend::Transliterator
  10. # Accepts a list of paths to translation files. Loads translations from
  11. # plain Ruby (*.rb), YAML files (*.yml), or JSON files (*.json). See #load_rb, #load_yml, and #load_json
  12. # for details.
  13. 1 def load_translations(*filenames)
  14. filenames = I18n.load_path if filenames.empty?
  15. filenames.flatten.each { |filename| load_file(filename) }
  16. end
  17. # This method receives a locale, a data hash and options for storing translations.
  18. # Should be implemented
  19. 1 def store_translations(locale, data, options = EMPTY_HASH)
  20. raise NotImplementedError
  21. end
  22. 1 def translate(locale, key, options = EMPTY_HASH)
  23. raise I18n::ArgumentError if (key.is_a?(String) || key.is_a?(Symbol)) && key.empty?
  24. raise InvalidLocale.new(locale) unless locale
  25. return nil if key.nil? && !options.key?(:default)
  26. entry = lookup(locale, key, options[:scope], options) unless key.nil?
  27. if entry.nil? && options.key?(:default)
  28. entry = default(locale, key, options[:default], options)
  29. else
  30. entry = resolve(locale, key, entry, options)
  31. end
  32. count = options[:count]
  33. if entry.nil? && (subtrees? || !count)
  34. if (options.key?(:default) && !options[:default].nil?) || !options.key?(:default)
  35. throw(:exception, I18n::MissingTranslation.new(locale, key, options))
  36. end
  37. end
  38. entry = entry.dup if entry.is_a?(String)
  39. entry = pluralize(locale, entry, count) if count
  40. if entry.nil? && !subtrees?
  41. throw(:exception, I18n::MissingTranslation.new(locale, key, options))
  42. end
  43. deep_interpolation = options[:deep_interpolation]
  44. values = options.except(*RESERVED_KEYS)
  45. if values
  46. entry = if deep_interpolation
  47. deep_interpolate(locale, entry, values)
  48. else
  49. interpolate(locale, entry, values)
  50. end
  51. end
  52. entry
  53. end
  54. 1 def exists?(locale, key)
  55. lookup(locale, key) != nil
  56. end
  57. # Acts the same as +strftime+, but uses a localized version of the
  58. # format string. Takes a key from the date/time formats translations as
  59. # a format argument (<em>e.g.</em>, <tt>:short</tt> in <tt>:'date.formats'</tt>).
  60. 1 def localize(locale, object, format = :default, options = EMPTY_HASH)
  61. if object.nil? && options.include?(:default)
  62. return options[:default]
  63. end
  64. raise ArgumentError, "Object must be a Date, DateTime or Time object. #{object.inspect} given." unless object.respond_to?(:strftime)
  65. if Symbol === format
  66. key = format
  67. type = object.respond_to?(:sec) ? 'time' : 'date'
  68. options = options.merge(:raise => true, :object => object, :locale => locale)
  69. format = I18n.t(:"#{type}.formats.#{key}", options)
  70. end
  71. format = translate_localization_format(locale, object, format, options)
  72. object.strftime(format)
  73. end
  74. # Returns an array of locales for which translations are available
  75. # ignoring the reserved translation meta data key :i18n.
  76. 1 def available_locales
  77. raise NotImplementedError
  78. end
  79. 1 def reload!
  80. 3 eager_load! if eager_loaded?
  81. end
  82. 1 def eager_load!
  83. @eager_loaded = true
  84. end
  85. 1 protected
  86. 1 def eager_loaded?
  87. 3 @eager_loaded ||= false
  88. end
  89. # The method which actually looks up for the translation in the store.
  90. 1 def lookup(locale, key, scope = [], options = EMPTY_HASH)
  91. raise NotImplementedError
  92. end
  93. 1 def subtrees?
  94. true
  95. end
  96. # Evaluates defaults.
  97. # If given subject is an Array, it walks the array and returns the
  98. # first translation that can be resolved. Otherwise it tries to resolve
  99. # the translation directly.
  100. 1 def default(locale, object, subject, options = EMPTY_HASH)
  101. options = options.dup.reject { |key, value| key == :default }
  102. case subject
  103. when Array
  104. subject.each do |item|
  105. result = resolve(locale, object, item, options)
  106. return result unless result.nil?
  107. end and nil
  108. else
  109. resolve(locale, object, subject, options)
  110. end
  111. end
  112. # Resolves a translation.
  113. # If the given subject is a Symbol, it will be translated with the
  114. # given options. If it is a Proc then it will be evaluated. All other
  115. # subjects will be returned directly.
  116. 1 def resolve(locale, object, subject, options = EMPTY_HASH)
  117. return subject if options[:resolve] == false
  118. result = catch(:exception) do
  119. case subject
  120. when Symbol
  121. I18n.translate(subject, options.merge(:locale => locale, :throw => true))
  122. when Proc
  123. date_or_time = options.delete(:object) || object
  124. resolve(locale, object, subject.call(date_or_time, options))
  125. else
  126. subject
  127. end
  128. end
  129. result unless result.is_a?(MissingTranslation)
  130. end
  131. # Picks a translation from a pluralized mnemonic subkey according to English
  132. # pluralization rules :
  133. # - It will pick the :one subkey if count is equal to 1.
  134. # - It will pick the :other subkey otherwise.
  135. # - It will pick the :zero subkey in the special case where count is
  136. # equal to 0 and there is a :zero subkey present. This behaviour is
  137. # not standard with regards to the CLDR pluralization rules.
  138. # Other backends can implement more flexible or complex pluralization rules.
  139. 1 def pluralize(locale, entry, count)
  140. return entry unless entry.is_a?(Hash) && count
  141. key = pluralization_key(entry, count)
  142. raise InvalidPluralizationData.new(entry, count, key) unless entry.has_key?(key)
  143. entry[key]
  144. end
  145. # Interpolates values into a given subject.
  146. #
  147. # if the given subject is a string then:
  148. # method interpolates "file %{file} opened by %%{user}", :file => 'test.txt', :user => 'Mr. X'
  149. # # => "file test.txt opened by %{user}"
  150. #
  151. # if the given subject is an array then:
  152. # each element of the array is recursively interpolated (until it finds a string)
  153. # method interpolates ["yes, %{user}", ["maybe no, %{user}, "no, %{user}"]], :user => "bartuz"
  154. # # => "["yes, bartuz",["maybe no, bartuz", "no, bartuz"]]"
  155. 1 def interpolate(locale, subject, values = EMPTY_HASH)
  156. return subject if values.empty?
  157. case subject
  158. when ::String then I18n.interpolate(subject, values)
  159. when ::Array then subject.map { |element| interpolate(locale, element, values) }
  160. else
  161. subject
  162. end
  163. end
  164. # Deep interpolation
  165. #
  166. # deep_interpolate { people: { ann: "Ann is %{ann}", john: "John is %{john}" } },
  167. # ann: 'good', john: 'big'
  168. # #=> { people: { ann: "Ann is good", john: "John is big" } }
  169. 1 def deep_interpolate(locale, data, values = EMPTY_HASH)
  170. return data if values.empty?
  171. case data
  172. when ::String
  173. I18n.interpolate(data, values)
  174. when ::Hash
  175. data.each_with_object({}) do |(k, v), result|
  176. result[k] = deep_interpolate(locale, v, values)
  177. end
  178. when ::Array
  179. data.map do |v|
  180. deep_interpolate(locale, v, values)
  181. end
  182. else
  183. data
  184. end
  185. end
  186. # Loads a single translations file by delegating to #load_rb or
  187. # #load_yml depending on the file extension and directly merges the
  188. # data to the existing translations. Raises I18n::UnknownFileType
  189. # for all other file extensions.
  190. 1 def load_file(filename)
  191. type = File.extname(filename).tr('.', '').downcase
  192. raise UnknownFileType.new(type, filename) unless respond_to?(:"load_#{type}", true)
  193. data = send(:"load_#{type}", filename)
  194. unless data.is_a?(Hash)
  195. raise InvalidLocaleData.new(filename, 'expects it to return a hash, but does not')
  196. end
  197. data.each { |locale, d| store_translations(locale, d || {}) }
  198. end
  199. # Loads a plain Ruby translations file. eval'ing the file must yield
  200. # a Hash containing translation data with locales as toplevel keys.
  201. 1 def load_rb(filename)
  202. eval(IO.read(filename), binding, filename)
  203. end
  204. # Loads a YAML translations file. The data must have locales as
  205. # toplevel keys.
  206. 1 def load_yml(filename)
  207. begin
  208. YAML.load_file(filename)
  209. rescue TypeError, ScriptError, StandardError => e
  210. raise InvalidLocaleData.new(filename, e.inspect)
  211. end
  212. end
  213. 1 alias_method :load_yaml, :load_yml
  214. # Loads a JSON translations file. The data must have locales as
  215. # toplevel keys.
  216. 1 def load_json(filename)
  217. begin
  218. ::JSON.parse(File.read(filename))
  219. rescue TypeError, StandardError => e
  220. raise InvalidLocaleData.new(filename, e.inspect)
  221. end
  222. end
  223. 1 def translate_localization_format(locale, object, format, options)
  224. format.to_s.gsub(/%(|\^)[aAbBpP]/) do |match|
  225. case match
  226. when '%a' then I18n.t!(:"date.abbr_day_names", :locale => locale, :format => format)[object.wday]
  227. when '%^a' then I18n.t!(:"date.abbr_day_names", :locale => locale, :format => format)[object.wday].upcase
  228. when '%A' then I18n.t!(:"date.day_names", :locale => locale, :format => format)[object.wday]
  229. when '%^A' then I18n.t!(:"date.day_names", :locale => locale, :format => format)[object.wday].upcase
  230. when '%b' then I18n.t!(:"date.abbr_month_names", :locale => locale, :format => format)[object.mon]
  231. when '%^b' then I18n.t!(:"date.abbr_month_names", :locale => locale, :format => format)[object.mon].upcase
  232. when '%B' then I18n.t!(:"date.month_names", :locale => locale, :format => format)[object.mon]
  233. when '%^B' then I18n.t!(:"date.month_names", :locale => locale, :format => format)[object.mon].upcase
  234. when '%p' then I18n.t!(:"time.#{object.hour < 12 ? :am : :pm}", :locale => locale, :format => format).upcase if object.respond_to? :hour
  235. when '%P' then I18n.t!(:"time.#{object.hour < 12 ? :am : :pm}", :locale => locale, :format => format).downcase if object.respond_to? :hour
  236. end
  237. end
  238. rescue MissingTranslationData => e
  239. e.message
  240. end
  241. 1 def pluralization_key(entry, count)
  242. key = :zero if count == 0 && entry.has_key?(:zero)
  243. key ||= count == 1 ? :one : :other
  244. end
  245. end
  246. end
  247. end

target/rubygems/gems/i18n-1.6.0/lib/i18n/backend/simple.rb

43.75% lines covered

48 relevant lines. 21 lines covered and 27 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require 'i18n/backend/base'
  3. 1 module I18n
  4. 1 module Backend
  5. # A simple backend that reads translations from YAML files and stores them in
  6. # an in-memory hash. Relies on the Base backend.
  7. #
  8. # The implementation is provided by a Implementation module allowing to easily
  9. # extend Simple backend's behavior by including modules. E.g.:
  10. #
  11. # module I18n::Backend::Pluralization
  12. # def pluralize(*args)
  13. # # extended pluralization logic
  14. # super
  15. # end
  16. # end
  17. #
  18. # I18n::Backend::Simple.include(I18n::Backend::Pluralization)
  19. 1 class Simple
  20. 1 using I18n::HashRefinements
  21. 3 (class << self; self; end).class_eval { public :include }
  22. 1 module Implementation
  23. 1 include Base
  24. 1 def initialized?
  25. @initialized ||= false
  26. end
  27. # Stores translations for the given locale in memory.
  28. # This uses a deep merge for the translations hash, so existing
  29. # translations will be overwritten by new ones only at the deepest
  30. # level of the hash.
  31. 1 def store_translations(locale, data, options = EMPTY_HASH)
  32. if I18n.enforce_available_locales &&
  33. I18n.available_locales_initialized? &&
  34. !I18n.available_locales.include?(locale.to_sym) &&
  35. !I18n.available_locales.include?(locale.to_s)
  36. return data
  37. end
  38. locale = locale.to_sym
  39. translations[locale] ||= {}
  40. data = data.deep_symbolize_keys
  41. translations[locale].deep_merge!(data)
  42. end
  43. # Get available locales from the translations hash
  44. 1 def available_locales
  45. init_translations unless initialized?
  46. translations.inject([]) do |locales, (locale, data)|
  47. locales << locale unless data.size <= 1 && (data.empty? || data.has_key?(:i18n))
  48. locales
  49. end
  50. end
  51. # Clean up translations hash and set initialized to false on reload!
  52. 1 def reload!
  53. 3 @initialized = false
  54. 3 @translations = nil
  55. 3 super
  56. end
  57. 1 def eager_load!
  58. init_translations unless initialized?
  59. super
  60. end
  61. 1 def translations(do_init: false)
  62. # To avoid returning empty translations,
  63. # call `init_translations`
  64. init_translations if do_init && !initialized?
  65. @translations ||= {}
  66. end
  67. 1 protected
  68. 1 def init_translations
  69. load_translations
  70. @initialized = true
  71. end
  72. # Looks up a translation from the translations hash. Returns nil if
  73. # either key is nil, or locale, scope or key do not exist as a key in the
  74. # nested translations hash. Splits keys or scopes containing dots
  75. # into multiple keys, i.e. <tt>currency.format</tt> is regarded the same as
  76. # <tt>%w(currency format)</tt>.
  77. 1 def lookup(locale, key, scope = [], options = EMPTY_HASH)
  78. init_translations unless initialized?
  79. keys = I18n.normalize_keys(locale, key, scope, options[:separator])
  80. keys.inject(translations) do |result, _key|
  81. return nil unless result.is_a?(Hash)
  82. unless result.has_key?(_key)
  83. _key = _key.to_s.to_sym
  84. return nil unless result.has_key?(_key)
  85. end
  86. result = result[_key]
  87. result = resolve(locale, _key, result, options.merge(:scope => nil)) if result.is_a?(Symbol)
  88. result
  89. end
  90. end
  91. end
  92. 1 include Implementation
  93. end
  94. end
  95. end

target/rubygems/gems/i18n-1.6.0/lib/i18n/backend/transliterator.rb

42.11% lines covered

38 relevant lines. 16 lines covered and 22 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. 1 module I18n
  4. 1 module Backend
  5. 1 module Transliterator
  6. 1 DEFAULT_REPLACEMENT_CHAR = "?"
  7. # Given a locale and a UTF-8 string, return the locale's ASCII
  8. # approximation for the string.
  9. 1 def transliterate(locale, string, replacement = nil)
  10. @transliterators ||= {}
  11. @transliterators[locale] ||= Transliterator.get I18n.t(:'i18n.transliterate.rule',
  12. :locale => locale, :resolve => false, :default => {})
  13. @transliterators[locale].transliterate(string, replacement)
  14. end
  15. # Get a transliterator instance.
  16. 1 def self.get(rule = nil)
  17. if !rule || rule.kind_of?(Hash)
  18. HashTransliterator.new(rule)
  19. elsif rule.kind_of? Proc
  20. ProcTransliterator.new(rule)
  21. else
  22. raise I18n::ArgumentError, "Transliteration rule must be a proc or a hash."
  23. end
  24. end
  25. # A transliterator which accepts a Proc as its transliteration rule.
  26. 1 class ProcTransliterator
  27. 1 def initialize(rule)
  28. @rule = rule
  29. end
  30. 1 def transliterate(string, replacement = nil)
  31. @rule.call(string)
  32. end
  33. end
  34. # A transliterator which accepts a Hash of characters as its translation
  35. # rule.
  36. 1 class HashTransliterator
  37. DEFAULT_APPROXIMATIONS = {
  38. "��"=>"A", "��"=>"A", "��"=>"A", "��"=>"A", "��"=>"A", "��"=>"A", "��"=>"AE",
  39. "��"=>"C", "��"=>"E", "��"=>"E", "��"=>"E", "��"=>"E", "��"=>"I", "��"=>"I",
  40. "��"=>"I", "��"=>"I", "��"=>"D", "��"=>"N", "��"=>"O", "��"=>"O", "��"=>"O",
  41. "��"=>"O", "��"=>"O", "��"=>"x", "��"=>"O", "��"=>"U", "��"=>"U", "��"=>"U",
  42. "��"=>"U", "��"=>"Y", "��"=>"Th", "��"=>"ss", "��"=>"a", "��"=>"a", "��"=>"a",
  43. "��"=>"a", "��"=>"a", "��"=>"a", "��"=>"ae", "��"=>"c", "��"=>"e", "��"=>"e",
  44. "��"=>"e", "��"=>"e", "��"=>"i", "��"=>"i", "��"=>"i", "��"=>"i", "��"=>"d",
  45. "��"=>"n", "��"=>"o", "��"=>"o", "��"=>"o", "��"=>"o", "��"=>"o", "��"=>"o",
  46. "��"=>"u", "��"=>"u", "��"=>"u", "��"=>"u", "��"=>"y", "��"=>"th", "��"=>"y",
  47. "��"=>"A", "��"=>"a", "��"=>"A", "��"=>"a", "��"=>"A", "��"=>"a", "��"=>"C",
  48. "��"=>"c", "��"=>"C", "��"=>"c", "��"=>"C", "��"=>"c", "��"=>"C", "��"=>"c",
  49. "��"=>"D", "��"=>"d", "��"=>"D", "��"=>"d", "��"=>"E", "��"=>"e", "��"=>"E",
  50. "��"=>"e", "��"=>"E", "��"=>"e", "��"=>"E", "��"=>"e", "��"=>"E", "��"=>"e",
  51. "��"=>"G", "��"=>"g", "��"=>"G", "��"=>"g", "��"=>"G", "��"=>"g", "��"=>"G",
  52. "��"=>"g", "��"=>"H", "��"=>"h", "��"=>"H", "��"=>"h", "��"=>"I", "��"=>"i",
  53. "��"=>"I", "��"=>"i", "��"=>"I", "��"=>"i", "��"=>"I", "��"=>"i", "��"=>"I",
  54. "��"=>"i", "��"=>"IJ", "��"=>"ij", "��"=>"J", "��"=>"j", "��"=>"K", "��"=>"k",
  55. "��"=>"k", "��"=>"L", "��"=>"l", "��"=>"L", "��"=>"l", "��"=>"L", "��"=>"l",
  56. "��"=>"L", "��"=>"l", "��"=>"L", "��"=>"l", "��"=>"N", "��"=>"n", "��"=>"N",
  57. "��"=>"n", "��"=>"N", "��"=>"n", "��"=>"'n", "��"=>"NG", "��"=>"ng",
  58. "��"=>"O", "��"=>"o", "��"=>"O", "��"=>"o", "��"=>"O", "��"=>"o", "��"=>"OE",
  59. "��"=>"oe", "��"=>"R", "��"=>"r", "��"=>"R", "��"=>"r", "��"=>"R", "��"=>"r",
  60. "��"=>"S", "��"=>"s", "��"=>"S", "��"=>"s", "��"=>"S", "��"=>"s", "��"=>"S",
  61. "��"=>"s", "��"=>"T", "��"=>"t", "��"=>"T", "��"=>"t", "��"=>"T", "��"=>"t",
  62. "��"=>"U", "��"=>"u", "��"=>"U", "��"=>"u", "��"=>"U", "��"=>"u", "��"=>"U",
  63. "��"=>"u", "��"=>"U", "��"=>"u", "��"=>"U", "��"=>"u", "��"=>"W", "��"=>"w",
  64. "��"=>"Y", "��"=>"y", "��"=>"Y", "��"=>"Z", "��"=>"z", "��"=>"Z", "��"=>"z",
  65. "��"=>"Z", "��"=>"z"
  66. }.freeze
  67. 1 def initialize(rule = nil)
  68. @rule = rule
  69. add_default_approximations
  70. add rule if rule
  71. end
  72. 1 def transliterate(string, replacement = nil)
  73. replacement ||= DEFAULT_REPLACEMENT_CHAR
  74. string.gsub(/[^\x00-\x7f]/u) do |char|
  75. approximations[char] || replacement
  76. end
  77. end
  78. 1 private
  79. 1 def approximations
  80. @approximations ||= {}
  81. end
  82. 1 def add_default_approximations
  83. DEFAULT_APPROXIMATIONS.each do |key, value|
  84. approximations[key] = value
  85. end
  86. end
  87. # Add transliteration rules to the approximations hash.
  88. 1 def add(hash)
  89. hash.each do |key, value|
  90. approximations[key.to_s] = value.to_s
  91. end
  92. end
  93. end
  94. end
  95. end
  96. end

target/rubygems/gems/i18n-1.6.0/lib/i18n/core_ext/hash.rb

44.0% lines covered

25 relevant lines. 11 lines covered and 14 lines missed.
    
  1. 1 module I18n
  2. 1 module HashRefinements
  3. 1 refine Hash do
  4. 1 using I18n::HashRefinements
  5. 1 def except(*keys)
  6. dup.except!(*keys)
  7. end
  8. 1 def except!(*keys)
  9. keys.each { |key| delete(key) }
  10. self
  11. end
  12. 1 def deep_symbolize_keys
  13. each_with_object({}) do |(key, value), result|
  14. result[symbolize_key(key)] = deep_symbolize_keys_in_object(value)
  15. result
  16. end
  17. end
  18. # deep_merge_hash! by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809
  19. 1 def deep_merge!(data)
  20. merger = lambda do |_key, v1, v2|
  21. Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2
  22. end
  23. merge!(data, &merger)
  24. end
  25. 1 def symbolize_key(key)
  26. key.respond_to?(:to_sym) ? key.to_sym : key
  27. end
  28. 1 private
  29. 1 def deep_symbolize_keys_in_object(value)
  30. case value
  31. when Hash
  32. value.deep_symbolize_keys
  33. when Array
  34. value.map { |e| deep_symbolize_keys_in_object(e) }
  35. else
  36. value
  37. end
  38. end
  39. end
  40. end
  41. end

target/rubygems/gems/jbuilder-2.9.1/lib/jbuilder/dependency_tracker.rb

75.0% lines covered

24 relevant lines. 18 lines covered and 6 lines missed.
    
  1. 1 require 'jbuilder/jbuilder'
  2. 1 dependency_tracker = false
  3. 1 begin
  4. 1 require 'action_view'
  5. 1 require 'action_view/dependency_tracker'
  6. 1 dependency_tracker = ::ActionView::DependencyTracker
  7. rescue LoadError
  8. begin
  9. require 'cache_digests'
  10. dependency_tracker = ::CacheDigests::DependencyTracker
  11. rescue LoadError
  12. end
  13. end
  14. 1 if dependency_tracker
  15. 1 class Jbuilder
  16. 1 module DependencyTrackerMethods
  17. # Matches:
  18. # json.partial! "messages/message"
  19. # json.partial!('messages/message')
  20. #
  21. 1 DIRECT_RENDERS = /
  22. \w+\.partial! # json.partial!
  23. \(?\s* # optional parenthesis
  24. (['"])([^'"]+)\1 # quoted value
  25. /x
  26. # Matches:
  27. # json.partial! partial: "comments/comment"
  28. # json.comments @post.comments, partial: "comments/comment", as: :comment
  29. # json.array! @posts, partial: "posts/post", as: :post
  30. # = render partial: "account"
  31. #
  32. 1 INDIRECT_RENDERS = /
  33. (?::partial\s*=>|partial:) # partial: or :partial =>
  34. \s* # optional whitespace
  35. (['"])([^'"]+)\1 # quoted value
  36. /x
  37. 1 def dependencies
  38. direct_dependencies + indirect_dependencies + explicit_dependencies
  39. end
  40. 1 private
  41. 1 def direct_dependencies
  42. source.scan(DIRECT_RENDERS).map(&:second)
  43. end
  44. 1 def indirect_dependencies
  45. source.scan(INDIRECT_RENDERS).map(&:second)
  46. end
  47. end
  48. end
  49. 1 ::Jbuilder::DependencyTracker = Class.new(dependency_tracker::ERBTracker)
  50. 1 ::Jbuilder::DependencyTracker.send :include, ::Jbuilder::DependencyTrackerMethods
  51. 1 dependency_tracker.register_tracker :jbuilder, ::Jbuilder::DependencyTracker
  52. end

target/rubygems/gems/jdbc-postgres-42.2.6/lib/jdbc/postgres.rb

66.67% lines covered

33 relevant lines. 22 lines covered and 11 lines missed.
    
  1. 1 require 'jdbc/postgres/version'
  2. 1 module Jdbc
  3. 1 module Postgres
  4. 1 def self.driver_jar
  5. 1 version_jre_version = DRIVER_VERSION.split( '.' )
  6. 1 version = jre_version
  7. 1 version_jre_version << (version ? ".jre#{version}" : '')
  8. 1 'postgresql-%s.%s.%s%s.jar' % version_jre_version
  9. end
  10. 1 def self.load_driver(method = :load)
  11. 1 send method, driver_jar
  12. rescue LoadError => e
  13. if (version = jre_version) && version < 6
  14. warn "failed to load postgresql (driver) jar, please note that we no longer " <<
  15. "include JDBC 3.x support, on Java < 6 please use gem 'jdbc-postgres', '~> 9.2'"
  16. end
  17. raise e
  18. end
  19. 1 def self.driver_name
  20. 1 'org.postgresql.Driver'
  21. end
  22. 1 private
  23. 1 def self.jre_version
  24. 1 version = ENV_JAVA[ 'java.specification.version' ]
  25. 1 version = version.split('.').last.to_i # '1.7' => 7, '9' => 9
  26. 1 if version < 6
  27. 5 # not supported
  28. elsif version == 6
  29. 6
  30. elsif version == 7
  31. 7
  32. else
  33. 1 nil # non-tagged X.Y.Z.jar
  34. end
  35. end
  36. 2 class << self; private :jre_version end
  37. 1 if defined?(JRUBY_VERSION) && # enable backwards-compat behavior :
  38. 1 ( Java::JavaLang::Boolean.get_boolean("jdbc.driver.autoload") ||
  39. Java::JavaLang::Boolean.get_boolean("jdbc.postgres.autoload") )
  40. warn "autoloading JDBC driver on require 'jdbc/postgres'" if $VERBOSE
  41. load_driver :require
  42. end
  43. end
  44. 1 PostgreSQL = Postgres unless const_defined?(:PostgreSQL)
  45. end

target/rubygems/gems/jdbc-postgres-42.2.6/lib/jdbc/postgres/version.rb

100.0% lines covered

4 relevant lines. 4 lines covered and 0 lines missed.
    
  1. 1 module Jdbc
  2. 1 module Postgres
  3. 1 DRIVER_VERSION = '42.2.6'
  4. 1 VERSION = DRIVER_VERSION
  5. end
  6. end

target/rubygems/gems/mail-2.7.1/lib/mail.rb

87.5% lines covered

48 relevant lines. 42 lines covered and 6 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. 1 module Mail # :doc:
  4. 1 require 'date'
  5. 1 require 'shellwords'
  6. 1 require 'uri'
  7. 1 require 'net/smtp'
  8. 1 require 'mini_mime'
  9. 1 if RUBY_VERSION <= '1.8.6'
  10. begin
  11. require 'tlsmail'
  12. rescue LoadError
  13. raise "You need to install tlsmail if you are using ruby <= 1.8.6"
  14. end
  15. end
  16. 1 if RUBY_VERSION >= "1.9.0"
  17. 1 require 'mail/version_specific/ruby_1_9'
  18. 1 RubyVer = Ruby19
  19. else
  20. require 'mail/version_specific/ruby_1_8'
  21. RubyVer = Ruby18
  22. end
  23. 1 require 'mail/version'
  24. 1 require 'mail/core_extensions/string'
  25. 1 require 'mail/core_extensions/smtp'
  26. 1 require 'mail/indifferent_hash'
  27. 1 require 'mail/multibyte'
  28. 1 require 'mail/constants'
  29. 1 require 'mail/utilities'
  30. 1 require 'mail/configuration'
  31. 1 @@autoloads = {}
  32. 1 def self.register_autoload(name, path)
  33. 55 @@autoloads[name] = path
  34. 55 autoload(name, path)
  35. end
  36. # This runs through the autoload list and explictly requires them for you.
  37. # Useful when running mail in a threaded process.
  38. #
  39. # Usage:
  40. #
  41. # require 'mail'
  42. # Mail.eager_autoload!
  43. 1 def self.eager_autoload!
  44. @@autoloads.each { |_,path| require(path) }
  45. end
  46. # Autoload mail send and receive classes.
  47. 1 require 'mail/network'
  48. 1 require 'mail/message'
  49. 1 require 'mail/part'
  50. 1 require 'mail/header'
  51. 1 require 'mail/parts_list'
  52. 1 require 'mail/attachments_list'
  53. 1 require 'mail/body'
  54. 1 require 'mail/field'
  55. 1 require 'mail/field_list'
  56. 1 require 'mail/envelope'
  57. 1 register_autoload :Parsers, "mail/parsers"
  58. # Autoload header field elements and transfer encodings.
  59. 1 require 'mail/elements'
  60. 1 require 'mail/encodings'
  61. 1 require 'mail/encodings/base64'
  62. 1 require 'mail/encodings/quoted_printable'
  63. 1 require 'mail/encodings/unix_to_unix'
  64. 1 require 'mail/matchers/has_sent_mail'
  65. 1 require 'mail/matchers/attachment_matchers.rb'
  66. # Finally... require all the Mail.methods
  67. 1 require 'mail/mail'
  68. end

target/rubygems/gems/mail-2.7.1/lib/mail/attachments_list.rb

14.04% lines covered

57 relevant lines. 8 lines covered and 49 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Mail
  3. 1 class AttachmentsList < Array
  4. 1 def initialize(parts_list)
  5. @parts_list = parts_list
  6. @content_disposition_type = 'attachment'
  7. parts_list.map { |p|
  8. if p.mime_type == 'message/rfc822'
  9. Mail.new(p.body.encoded).attachments
  10. elsif p.parts.empty?
  11. p if p.attachment?
  12. else
  13. p.attachments
  14. end
  15. }.flatten.compact.each { |a| self << a }
  16. self
  17. end
  18. 1 def inline
  19. @content_disposition_type = 'inline'
  20. self
  21. end
  22. # Returns the attachment by filename or at index.
  23. #
  24. # mail.attachments['test.png'] = File.read('test.png')
  25. # mail.attachments['test.jpg'] = File.read('test.jpg')
  26. #
  27. # mail.attachments['test.png'].filename #=> 'test.png'
  28. # mail.attachments[1].filename #=> 'test.jpg'
  29. 1 def [](index_value)
  30. if index_value.is_a?(Integer)
  31. self.fetch(index_value)
  32. else
  33. self.select { |a| a.filename == index_value }.first
  34. end
  35. end
  36. 1 def []=(name, value)
  37. encoded_name = Mail::Encodings.decode_encode name, :encode
  38. default_values = { :content_type => "#{set_mime_type(name)}; filename=\"#{encoded_name}\"",
  39. :content_transfer_encoding => "#{guess_encoding}",
  40. :content_disposition => "#{@content_disposition_type}; filename=\"#{encoded_name}\"" }
  41. if value.is_a?(Hash)
  42. if path = value.delete(:filename)
  43. value[:content] ||= File.open(path, 'rb') { |f| f.read }
  44. end
  45. default_values[:body] = value.delete(:content) if value[:content]
  46. default_values[:body] = value.delete(:data) if value[:data]
  47. encoding = value.delete(:transfer_encoding) || value.delete(:encoding)
  48. if encoding
  49. if Mail::Encodings.defined? encoding
  50. default_values[:content_transfer_encoding] = encoding
  51. else
  52. raise "Do not know how to handle Content Transfer Encoding #{encoding}, please choose either quoted-printable or base64"
  53. end
  54. end
  55. if value[:mime_type]
  56. default_values[:content_type] = value.delete(:mime_type)
  57. @mime_type = MiniMime.lookup_by_content_type(default_values[:content_type])
  58. default_values[:content_transfer_encoding] ||= guess_encoding
  59. end
  60. hash = default_values.merge(value)
  61. else
  62. default_values[:body] = value
  63. hash = default_values
  64. end
  65. if hash[:body].respond_to? :force_encoding and hash[:body].respond_to? :valid_encoding?
  66. if not hash[:body].valid_encoding? and default_values[:content_transfer_encoding].downcase == "binary"
  67. hash[:body] = hash[:body].dup if hash[:body].frozen?
  68. hash[:body].force_encoding("BINARY")
  69. end
  70. end
  71. attachment = Part.new(hash)
  72. attachment.add_content_id(hash[:content_id])
  73. @parts_list << attachment
  74. end
  75. # Uses the mime type to try and guess the encoding, if it is a binary type, or unknown, then we
  76. # set it to binary, otherwise as set to plain text
  77. 1 def guess_encoding
  78. if @mime_type && !@mime_type.binary?
  79. "7bit"
  80. else
  81. "binary"
  82. end
  83. end
  84. 1 def set_mime_type(filename)
  85. # Have to do this because MIME::Types is not Ruby 1.9 safe yet
  86. if RUBY_VERSION >= '1.9'
  87. filename = filename.encode(Encoding::UTF_8) if filename.respond_to?(:encode)
  88. end
  89. @mime_type = MiniMime.lookup_by_filename(filename)
  90. @mime_type && @mime_type.content_type
  91. end
  92. end
  93. end

target/rubygems/gems/mail-2.7.1/lib/mail/body.rb

29.27% lines covered

123 relevant lines. 36 lines covered and 87 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. 1 module Mail
  4. # = Body
  5. #
  6. # The body is where the text of the email is stored. Mail treats the body
  7. # as a single object. The body itself has no information about boundaries
  8. # used in the MIME standard, it just looks at its content as either a single
  9. # block of text, or (if it is a multipart message) as an array of blocks of text.
  10. #
  11. # A body has to be told to split itself up into a multipart message by calling
  12. # #split with the correct boundary. This is because the body object has no way
  13. # of knowing what the correct boundary is for itself (there could be many
  14. # boundaries in a body in the case of a nested MIME text).
  15. #
  16. # Once split is called, Mail::Body will slice itself up on this boundary,
  17. # assigning anything that appears before the first part to the preamble, and
  18. # anything that appears after the closing boundary to the epilogue, then
  19. # each part gets initialized into a Mail::Part object.
  20. #
  21. # The boundary that is used to split up the Body is also stored in the Body
  22. # object for use on encoding itself back out to a string. You can
  23. # overwrite this if it needs to be changed.
  24. #
  25. # On encoding, the body will return the preamble, then each part joined by
  26. # the boundary, followed by a closing boundary string and then the epilogue.
  27. 1 class Body
  28. 1 def initialize(string = '')
  29. @boundary = nil
  30. @preamble = nil
  31. @epilogue = nil
  32. @charset = nil
  33. @part_sort_order = [ "text/plain", "text/enriched", "text/html", "multipart/alternative" ]
  34. @parts = Mail::PartsList.new
  35. if Utilities.blank?(string)
  36. @raw_source = ''
  37. else
  38. # Do join first incase we have been given an Array in Ruby 1.9
  39. if string.respond_to?(:join)
  40. @raw_source = ::Mail::Utilities.to_crlf(string.join(''))
  41. elsif string.respond_to?(:to_s)
  42. @raw_source = ::Mail::Utilities.to_crlf(string.to_s)
  43. else
  44. raise "You can only assign a string or an object that responds_to? :join or :to_s to a body."
  45. end
  46. end
  47. @encoding = default_encoding
  48. set_charset
  49. end
  50. # Matches this body with another body. Also matches the decoded value of this
  51. # body with a string.
  52. #
  53. # Examples:
  54. #
  55. # body = Mail::Body.new('The body')
  56. # body == body #=> true
  57. #
  58. # body = Mail::Body.new('The body')
  59. # body == 'The body' #=> true
  60. #
  61. # body = Mail::Body.new("VGhlIGJvZHk=\n")
  62. # body.encoding = 'base64'
  63. # body == "The body" #=> true
  64. 1 def ==(other)
  65. if other.class == String
  66. self.decoded == other
  67. else
  68. super
  69. end
  70. end
  71. # Accepts a string and performs a regular expression against the decoded text
  72. #
  73. # Examples:
  74. #
  75. # body = Mail::Body.new('The body')
  76. # body =~ /The/ #=> 0
  77. #
  78. # body = Mail::Body.new("VGhlIGJvZHk=\n")
  79. # body.encoding = 'base64'
  80. # body =~ /The/ #=> 0
  81. 1 def =~(regexp)
  82. self.decoded =~ regexp
  83. end
  84. # Accepts a string and performs a regular expression against the decoded text
  85. #
  86. # Examples:
  87. #
  88. # body = Mail::Body.new('The body')
  89. # body.match(/The/) #=> #<MatchData "The">
  90. #
  91. # body = Mail::Body.new("VGhlIGJvZHk=\n")
  92. # body.encoding = 'base64'
  93. # body.match(/The/) #=> #<MatchData "The">
  94. 1 def match(regexp)
  95. self.decoded.match(regexp)
  96. end
  97. # Accepts anything that responds to #to_s and checks if it's a substring of the decoded text
  98. #
  99. # Examples:
  100. #
  101. # body = Mail::Body.new('The body')
  102. # body.include?('The') #=> true
  103. #
  104. # body = Mail::Body.new("VGhlIGJvZHk=\n")
  105. # body.encoding = 'base64'
  106. # body.include?('The') #=> true
  107. 1 def include?(other)
  108. self.decoded.include?(other.to_s)
  109. end
  110. # Allows you to set the sort order of the parts, overriding the default sort order.
  111. # Defaults to 'text/plain', then 'text/enriched', then 'text/html', then 'multipart/alternative'
  112. # with any other content type coming after.
  113. 1 def set_sort_order(order)
  114. @part_sort_order = order
  115. end
  116. # Allows you to sort the parts according to the default sort order, or the sort order you
  117. # set with :set_sort_order.
  118. #
  119. # sort_parts! is also called from :encode, so there is no need for you to call this explicitly
  120. 1 def sort_parts!
  121. @parts.each do |p|
  122. p.body.set_sort_order(@part_sort_order)
  123. p.body.sort_parts!
  124. end
  125. @parts.sort!(@part_sort_order)
  126. end
  127. # Returns the raw source that the body was initialized with, without
  128. # any tampering
  129. 1 def raw_source
  130. @raw_source
  131. end
  132. 1 def negotiate_best_encoding(message_encoding, allowed_encodings = nil)
  133. Mail::Encodings::TransferEncoding.negotiate(message_encoding, encoding, raw_source, allowed_encodings)
  134. end
  135. # Returns a body encoded using transfer_encoding. Multipart always uses an
  136. # identiy encoding (i.e. no encoding).
  137. # Calling this directly is not a good idea, but supported for compatibility
  138. # TODO: Validate that preamble and epilogue are valid for requested encoding
  139. 1 def encoded(transfer_encoding = nil)
  140. if multipart?
  141. self.sort_parts!
  142. encoded_parts = parts.map { |p| p.encoded }
  143. ([preamble] + encoded_parts).join(crlf_boundary) + end_boundary + epilogue.to_s
  144. else
  145. dec = Mail::Encodings.get_encoding(encoding)
  146. enc =
  147. if Utilities.blank?(transfer_encoding)
  148. dec
  149. else
  150. negotiate_best_encoding(transfer_encoding)
  151. end
  152. if dec.nil?
  153. # Cannot decode, so skip normalization
  154. raw_source
  155. else
  156. # Decode then encode to normalize and allow transforming
  157. # from base64 to Q-P and vice versa
  158. decoded = dec.decode(raw_source)
  159. if defined?(Encoding) && charset && charset != "US-ASCII"
  160. decoded = decoded.encode(charset)
  161. decoded.force_encoding('BINARY') unless Encoding.find(charset).ascii_compatible?
  162. end
  163. enc.encode(decoded)
  164. end
  165. end
  166. end
  167. 1 def decoded
  168. if !Encodings.defined?(encoding)
  169. raise UnknownEncodingType, "Don't know how to decode #{encoding}, please call #encoded and decode it yourself."
  170. else
  171. Encodings.get_encoding(encoding).decode(raw_source)
  172. end
  173. end
  174. 1 def to_s
  175. decoded
  176. end
  177. 1 def charset
  178. @charset
  179. end
  180. 1 def charset=( val )
  181. @charset = val
  182. end
  183. 1 def encoding(val = nil)
  184. if val
  185. self.encoding = val
  186. else
  187. @encoding
  188. end
  189. end
  190. 1 def encoding=( val )
  191. @encoding =
  192. if val == "text" || Utilities.blank?(val)
  193. default_encoding
  194. else
  195. val
  196. end
  197. end
  198. # Returns the preamble (any text that is before the first MIME boundary)
  199. 1 def preamble
  200. @preamble
  201. end
  202. # Sets the preamble to a string (adds text before the first MIME boundary)
  203. 1 def preamble=( val )
  204. @preamble = val
  205. end
  206. # Returns the epilogue (any text that is after the last MIME boundary)
  207. 1 def epilogue
  208. @epilogue
  209. end
  210. # Sets the epilogue to a string (adds text after the last MIME boundary)
  211. 1 def epilogue=( val )
  212. @epilogue = val
  213. end
  214. # Returns true if there are parts defined in the body
  215. 1 def multipart?
  216. true unless parts.empty?
  217. end
  218. # Returns the boundary used by the body
  219. 1 def boundary
  220. @boundary
  221. end
  222. # Allows you to change the boundary of this Body object
  223. 1 def boundary=( val )
  224. @boundary = val
  225. end
  226. 1 def parts
  227. @parts
  228. end
  229. 1 def <<( val )
  230. if @parts
  231. @parts << val
  232. else
  233. @parts = Mail::PartsList.new[val]
  234. end
  235. end
  236. 1 def split!(boundary)
  237. self.boundary = boundary
  238. parts = extract_parts
  239. # Make the preamble equal to the preamble (if any)
  240. self.preamble = parts[0].to_s.strip
  241. # Make the epilogue equal to the epilogue (if any)
  242. self.epilogue = parts[-1].to_s.strip
  243. parts[1...-1].to_a.each { |part| @parts << Mail::Part.new(part) }
  244. self
  245. end
  246. 1 def ascii_only?
  247. unless defined? @ascii_only
  248. @ascii_only = raw_source.ascii_only?
  249. end
  250. @ascii_only
  251. end
  252. 1 def empty?
  253. !!raw_source.to_s.empty?
  254. end
  255. 1 def default_encoding
  256. ascii_only? ? '7bit' : '8bit'
  257. end
  258. 1 private
  259. # split parts by boundary, ignore first part if empty, append final part when closing boundary was missing
  260. 1 def extract_parts
  261. parts_regex = /
  262. (?: # non-capturing group
  263. \A | # start of string OR
  264. \r\n # line break
  265. )
  266. (
  267. --#{Regexp.escape(boundary || "")} # boundary delimiter
  268. (?:--)? # with non-capturing optional closing
  269. )
  270. (?=\s*$) # lookahead matching zero or more spaces followed by line-ending
  271. /x
  272. parts = raw_source.split(parts_regex).each_slice(2).to_a
  273. parts.each_with_index { |(part, _), index| parts.delete_at(index) if index > 0 && Utilities.blank?(part) }
  274. if parts.size > 1
  275. final_separator = parts[-2][1]
  276. parts << [""] if final_separator != "--#{boundary}--"
  277. end
  278. parts.map(&:first)
  279. end
  280. 1 def crlf_boundary
  281. "\r\n--#{boundary}\r\n"
  282. end
  283. 1 def end_boundary
  284. "\r\n--#{boundary}--\r\n"
  285. end
  286. 1 def set_charset
  287. @charset = ascii_only? ? 'US-ASCII' : nil
  288. end
  289. end
  290. end

target/rubygems/gems/mail-2.7.1/lib/mail/check_delivery_params.rb

31.03% lines covered

29 relevant lines. 9 lines covered and 20 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Mail
  3. 1 module CheckDeliveryParams #:nodoc:
  4. 1 class << self
  5. 1 def check(mail)
  6. [ check_from(mail.smtp_envelope_from),
  7. check_to(mail.smtp_envelope_to),
  8. check_message(mail) ]
  9. end
  10. 1 def check_from(addr)
  11. if Utilities.blank?(addr)
  12. raise ArgumentError, "SMTP From address may not be blank: #{addr.inspect}"
  13. end
  14. check_addr 'From', addr
  15. end
  16. 1 def check_to(addrs)
  17. if Utilities.blank?(addrs)
  18. raise ArgumentError, "SMTP To address may not be blank: #{addrs.inspect}"
  19. end
  20. Array(addrs).map do |addr|
  21. check_addr 'To', addr
  22. end
  23. end
  24. 1 def check_addr(addr_name, addr)
  25. validate_smtp_addr addr do |error_message|
  26. raise ArgumentError, "SMTP #{addr_name} address #{error_message}: #{addr.inspect}"
  27. end
  28. end
  29. 1 def validate_smtp_addr(addr)
  30. if addr
  31. if addr.bytesize > 2048
  32. yield 'may not exceed 2kB'
  33. end
  34. if /[\r\n]/ =~ addr
  35. yield 'may not contain CR or LF line breaks'
  36. end
  37. end
  38. addr
  39. end
  40. 1 def check_message(message)
  41. message = message.encoded if message.respond_to?(:encoded)
  42. if Utilities.blank?(message)
  43. raise ArgumentError, 'An encoded message is required to send an email'
  44. end
  45. message
  46. end
  47. end
  48. end
  49. end

target/rubygems/gems/mail-2.7.1/lib/mail/configuration.rb

29.41% lines covered

34 relevant lines. 10 lines covered and 24 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. #
  4. # Thanks to Nicolas Fouch�� for this wrapper
  5. #
  6. 1 require 'singleton'
  7. 1 module Mail
  8. # The Configuration class is a Singleton used to hold the default
  9. # configuration for all Mail objects.
  10. #
  11. # Each new mail object gets a copy of these values at initialization
  12. # which can be overwritten on a per mail object basis.
  13. 1 class Configuration
  14. 1 include Singleton
  15. 1 def initialize
  16. @delivery_method = nil
  17. @retriever_method = nil
  18. super
  19. end
  20. 1 def delivery_method(method = nil, settings = {})
  21. return @delivery_method if @delivery_method && method.nil?
  22. @delivery_method = lookup_delivery_method(method).new(settings)
  23. end
  24. 1 def lookup_delivery_method(method)
  25. case method.is_a?(String) ? method.to_sym : method
  26. when nil
  27. Mail::SMTP
  28. when :smtp
  29. Mail::SMTP
  30. when :sendmail
  31. Mail::Sendmail
  32. when :exim
  33. Mail::Exim
  34. when :file
  35. Mail::FileDelivery
  36. when :smtp_connection
  37. Mail::SMTPConnection
  38. when :test
  39. Mail::TestMailer
  40. when :logger
  41. Mail::LoggerDelivery
  42. else
  43. method
  44. end
  45. end
  46. 1 def retriever_method(method = nil, settings = {})
  47. return @retriever_method if @retriever_method && method.nil?
  48. @retriever_method = lookup_retriever_method(method).new(settings)
  49. end
  50. 1 def lookup_retriever_method(method)
  51. case method
  52. when nil
  53. Mail::POP3
  54. when :pop3
  55. Mail::POP3
  56. when :imap
  57. Mail::IMAP
  58. when :test
  59. Mail::TestRetriever
  60. else
  61. method
  62. end
  63. end
  64. 1 def param_encode_language(value = nil)
  65. value ? @encode_language = value : @encode_language ||= 'en'
  66. end
  67. end
  68. end

target/rubygems/gems/mail-2.7.1/lib/mail/constants.rb

100.0% lines covered

46 relevant lines. 46 lines covered and 0 lines missed.
    
  1. # encoding: us-ascii
  2. # frozen_string_literal: true
  3. 1 module Mail
  4. 1 module Constants
  5. 1 white_space = %Q|\x9\x20|
  6. 1 text = %Q|\x1-\x8\xB\xC\xE-\x7f|
  7. 1 field_name = %Q|\x21-\x39\x3b-\x7e|
  8. 1 qp_safe = %Q|\x20-\x3c\x3e-\x7e|
  9. 1 aspecial = %Q|()<>[]:;@\\,."| # RFC5322
  10. 1 tspecial = %Q|()<>@,;:\\"/[]?=| # RFC2045
  11. 1 sp = %Q| |
  12. 1 control = %Q|\x00-\x1f\x7f-\xff|
  13. 1 if control.respond_to?(:force_encoding)
  14. 1 control = control.dup.force_encoding(Encoding::BINARY)
  15. end
  16. 1 CRLF = /\r?\n/
  17. 1 WSP = /[#{white_space}]/
  18. 1 FWS = /#{CRLF}#{WSP}*/
  19. 1 TEXT = /[#{text}]/ # + obs-text
  20. 1 FIELD_NAME = /[#{field_name}]+/
  21. 1 FIELD_PREFIX = /\A(#{FIELD_NAME})/
  22. 1 FIELD_BODY = /.+/m
  23. 1 FIELD_LINE = /^[#{field_name}]+:\s*.+$/
  24. 1 FIELD_SPLIT = /^(#{FIELD_NAME})\s*:\s*(#{FIELD_BODY})?$/
  25. 1 HEADER_LINE = /^([#{field_name}]+:\s*.+)$/
  26. 1 HEADER_SPLIT = /#{CRLF}(?!#{WSP})/
  27. 1 QP_UNSAFE = /[^#{qp_safe}]/
  28. 1 QP_SAFE = /[#{qp_safe}]/
  29. 1 CONTROL_CHAR = /[#{control}]/n
  30. 1 ATOM_UNSAFE = /[#{Regexp.quote aspecial}#{control}#{sp}]/n
  31. 1 PHRASE_UNSAFE = /[#{Regexp.quote aspecial}#{control}]/n
  32. 1 TOKEN_UNSAFE = /[#{Regexp.quote tspecial}#{control}#{sp}]/n
  33. 1 ENCODED_VALUE = /\=\?([^?]+)\?([QB])\?[^?]*?\?\=/mi
  34. 1 FULL_ENCODED_VALUE = /(\=\?[^?]+\?[QB]\?[^?]*?\?\=)/mi
  35. 1 EMPTY = ''
  36. 1 SPACE = ' '
  37. 1 UNDERSCORE = '_'
  38. 1 HYPHEN = '-'
  39. 1 COLON = ':'
  40. 1 ASTERISK = '*'
  41. 1 CR = "\r"
  42. 1 LF = "\n"
  43. 1 CR_ENCODED = "=0D"
  44. 1 LF_ENCODED = "=0A"
  45. 1 CAPITAL_M = 'M'
  46. 1 EQUAL_LF = "=\n"
  47. 1 NULL_SENDER = '<>'
  48. 1 Q_VALUES = ['Q','q']
  49. 1 B_VALUES = ['B','b']
  50. end
  51. end

target/rubygems/gems/mail-2.7.1/lib/mail/core_extensions/smtp.rb

9.09% lines covered

11 relevant lines. 1 lines covered and 10 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. # This is a backport of r30294 from ruby trunk because of a bug in net/smtp.
  4. # http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=rev&amp;revision=30294
  5. #
  6. # Fixed in Ruby 1.9.3 - tlsconnect also does not exist in some early versions of ruby
  7. 1 if RUBY_VERSION < '1.9.3'
  8. module Net
  9. class SMTP
  10. begin
  11. alias_method :original_tlsconnect, :tlsconnect
  12. def tlsconnect(s)
  13. verified = false
  14. begin
  15. original_tlsconnect(s).tap { verified = true }
  16. ensure
  17. unless verified
  18. s.close rescue nil
  19. end
  20. end
  21. end
  22. rescue NameError
  23. end
  24. end
  25. end
  26. end

target/rubygems/gems/mail-2.7.1/lib/mail/core_extensions/string.rb

42.86% lines covered

7 relevant lines. 3 lines covered and 4 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. 1 class String #:nodoc:
  4. 1 unless method_defined?(:ascii_only?)
  5. # Backport from Ruby 1.9 checks for non-us-ascii characters.
  6. def ascii_only?
  7. self !~ MATCH_NON_US_ASCII
  8. end
  9. MATCH_NON_US_ASCII = /[^\x00-\x7f]/
  10. end
  11. 1 unless method_defined?(:bytesize)
  12. alias :bytesize :length
  13. end
  14. end

target/rubygems/gems/mail-2.7.1/lib/mail/elements.rb

100.0% lines covered

13 relevant lines. 13 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Mail
  3. 1 register_autoload :Address, 'mail/elements/address'
  4. 1 register_autoload :AddressList, 'mail/elements/address_list'
  5. 1 register_autoload :ContentDispositionElement, 'mail/elements/content_disposition_element'
  6. 1 register_autoload :ContentLocationElement, 'mail/elements/content_location_element'
  7. 1 register_autoload :ContentTransferEncodingElement, 'mail/elements/content_transfer_encoding_element'
  8. 1 register_autoload :ContentTypeElement, 'mail/elements/content_type_element'
  9. 1 register_autoload :DateTimeElement, 'mail/elements/date_time_element'
  10. 1 register_autoload :EnvelopeFromElement, 'mail/elements/envelope_from_element'
  11. 1 register_autoload :MessageIdsElement, 'mail/elements/message_ids_element'
  12. 1 register_autoload :MimeVersionElement, 'mail/elements/mime_version_element'
  13. 1 register_autoload :PhraseList, 'mail/elements/phrase_list'
  14. 1 register_autoload :ReceivedElement, 'mail/elements/received_element'
  15. end

target/rubygems/gems/mail-2.7.1/lib/mail/encodings.rb

26.19% lines covered

126 relevant lines. 33 lines covered and 93 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. 1 module Mail
  4. # Raised when attempting to decode an unknown encoding type
  5. 1 class UnknownEncodingType < StandardError #:nodoc:
  6. end
  7. 1 module Encodings
  8. 1 include Mail::Constants
  9. 1 extend Mail::Utilities
  10. 1 @transfer_encodings = {}
  11. # Register transfer encoding
  12. #
  13. # Example
  14. #
  15. # Encodings.register "base64", Mail::Encodings::Base64
  16. 1 def Encodings.register(name, cls)
  17. 8 @transfer_encodings[get_name(name)] = cls
  18. end
  19. # Is the encoding we want defined?
  20. #
  21. # Example:
  22. #
  23. # Encodings.defined?(:base64) #=> true
  24. 1 def Encodings.defined?(name)
  25. @transfer_encodings.include? get_name(name)
  26. end
  27. # Gets a defined encoding type, QuotedPrintable or Base64 for now.
  28. #
  29. # Each encoding needs to be defined as a Mail::Encodings::ClassName for
  30. # this to work, allows us to add other encodings in the future.
  31. #
  32. # Example:
  33. #
  34. # Encodings.get_encoding(:base64) #=> Mail::Encodings::Base64
  35. 1 def Encodings.get_encoding(name)
  36. @transfer_encodings[get_name(name)]
  37. end
  38. 1 def Encodings.get_all
  39. @transfer_encodings.values
  40. end
  41. 1 def Encodings.get_name(name)
  42. 8 underscoreize(name).downcase
  43. end
  44. 1 def Encodings.transcode_charset(str, from_charset, to_charset = 'UTF-8')
  45. if from_charset
  46. RubyVer.transcode_charset str, from_charset, to_charset
  47. else
  48. str
  49. end
  50. end
  51. # Encodes a parameter value using URI Escaping, note the language field 'en' can
  52. # be set using Mail::Configuration, like so:
  53. #
  54. # Mail.defaults do
  55. # param_encode_language 'jp'
  56. # end
  57. #
  58. # The character set used for encoding will either be the value of $KCODE for
  59. # Ruby < 1.9 or the encoding on the string passed in.
  60. #
  61. # Example:
  62. #
  63. # Mail::Encodings.param_encode("This is fun") #=> "us-ascii'en'This%20is%20fun"
  64. 1 def Encodings.param_encode(str)
  65. case
  66. when str.ascii_only? && str =~ TOKEN_UNSAFE
  67. %Q{"#{str}"}
  68. when str.ascii_only?
  69. str
  70. else
  71. RubyVer.param_encode(str)
  72. end
  73. end
  74. # Decodes a parameter value using URI Escaping.
  75. #
  76. # Example:
  77. #
  78. # Mail::Encodings.param_decode("This%20is%20fun", 'us-ascii') #=> "This is fun"
  79. #
  80. # str = Mail::Encodings.param_decode("This%20is%20fun", 'iso-8559-1')
  81. # str.encoding #=> 'ISO-8859-1' ## Only on Ruby 1.9
  82. # str #=> "This is fun"
  83. 1 def Encodings.param_decode(str, encoding)
  84. RubyVer.param_decode(str, encoding)
  85. end
  86. # Decodes or encodes a string as needed for either Base64 or QP encoding types in
  87. # the =?<encoding>?[QB]?<string>?=" format.
  88. #
  89. # The output type needs to be :decode to decode the input string or :encode to
  90. # encode the input string. The character set used for encoding will either be
  91. # the value of $KCODE for Ruby < 1.9 or the encoding on the string passed in.
  92. #
  93. # On encoding, will only send out Base64 encoded strings.
  94. 1 def Encodings.decode_encode(str, output_type)
  95. case
  96. when output_type == :decode
  97. Encodings.value_decode(str)
  98. else
  99. if str.ascii_only?
  100. str
  101. else
  102. Encodings.b_value_encode(str, find_encoding(str))
  103. end
  104. end
  105. end
  106. # Decodes a given string as Base64 or Quoted Printable, depending on what
  107. # type it is.
  108. #
  109. # String has to be of the format =?<encoding>?[QB]?<string>?=
  110. 1 def Encodings.value_decode(str)
  111. # Optimization: If there's no encoded-words in the string, just return it
  112. return str unless str =~ ENCODED_VALUE
  113. lines = collapse_adjacent_encodings(str)
  114. # Split on white-space boundaries with capture, so we capture the white-space as well
  115. lines.each do |line|
  116. line.gsub!(ENCODED_VALUE) do |string|
  117. case $2
  118. when *B_VALUES then b_value_decode(string)
  119. when *Q_VALUES then q_value_decode(string)
  120. end
  121. end
  122. end.join("")
  123. end
  124. # Takes an encoded string of the format =?<encoding>?[QB]?<string>?=
  125. 1 def Encodings.unquote_and_convert_to(str, to_encoding)
  126. output = value_decode( str ).to_s # output is already converted to UTF-8
  127. if 'utf8' == to_encoding.to_s.downcase.gsub("-", "")
  128. output
  129. elsif to_encoding
  130. begin
  131. if RUBY_VERSION >= '1.9'
  132. output.encode(to_encoding)
  133. else
  134. require 'iconv'
  135. Iconv.iconv(to_encoding, 'UTF-8', output).first
  136. end
  137. rescue Iconv::IllegalSequence, Iconv::InvalidEncoding, Errno::EINVAL
  138. # the 'from' parameter specifies a charset other than what the text
  139. # actually is...not much we can do in this case but just return the
  140. # unconverted text.
  141. #
  142. # Ditto if either parameter represents an unknown charset, like
  143. # X-UNKNOWN.
  144. output
  145. end
  146. else
  147. output
  148. end
  149. end
  150. 1 def Encodings.address_encode(address, charset = 'utf-8')
  151. if address.is_a?(Array)
  152. address.compact.map { |a| Encodings.address_encode(a, charset) }.join(", ")
  153. elsif address
  154. encode_non_usascii(address, charset)
  155. end
  156. end
  157. 1 def Encodings.encode_non_usascii(address, charset)
  158. return address if address.ascii_only? or charset.nil?
  159. # With KCODE=u we can't use regexps on other encodings. Go ASCII.
  160. with_ascii_kcode do
  161. # Encode all strings embedded inside of quotes
  162. address = address.gsub(/("[^"]*[^\/]")/) { |s| Encodings.b_value_encode(unquote(s), charset) }
  163. # Then loop through all remaining items and encode as needed
  164. tokens = address.split(/\s/)
  165. map_with_index(tokens) do |word, i|
  166. if word.ascii_only?
  167. word
  168. else
  169. previous_non_ascii = i>0 && tokens[i-1] && !tokens[i-1].ascii_only?
  170. if previous_non_ascii #why are we adding an extra space here?
  171. word = " #{word}"
  172. end
  173. Encodings.b_value_encode(word, charset)
  174. end
  175. end.join(' ')
  176. end
  177. end
  178. 1 if RUBY_VERSION < '1.9'
  179. # With KCODE=u we can't use regexps on other encodings. Go ASCII.
  180. def Encodings.with_ascii_kcode #:nodoc:
  181. if $KCODE
  182. $KCODE, original_kcode = '', $KCODE
  183. end
  184. yield
  185. ensure
  186. $KCODE = original_kcode if original_kcode
  187. end
  188. else
  189. 1 def Encodings.with_ascii_kcode #:nodoc:
  190. yield
  191. end
  192. end
  193. # Encode a string with Base64 Encoding and returns it ready to be inserted
  194. # as a value for a field, that is, in the =?<charset>?B?<string>?= format
  195. #
  196. # Example:
  197. #
  198. # Encodings.b_value_encode('This is ��� string', 'UTF-8')
  199. # #=> "=?UTF-8?B?VGhpcyBpcyDjgYIgc3RyaW5n?="
  200. 1 def Encodings.b_value_encode(string, encoding = nil)
  201. if string.to_s.ascii_only?
  202. string
  203. else
  204. Encodings.each_base64_chunk_byterange(string, 60).map do |chunk|
  205. str, encoding = RubyVer.b_value_encode(chunk, encoding)
  206. "=?#{encoding}?B?#{str.chomp}?="
  207. end.join(" ")
  208. end
  209. end
  210. # Encode a string with Quoted-Printable Encoding and returns it ready to be inserted
  211. # as a value for a field, that is, in the =?<charset>?Q?<string>?= format
  212. #
  213. # Example:
  214. #
  215. # Encodings.q_value_encode('This is ��� string', 'UTF-8')
  216. # #=> "=?UTF-8?Q?This_is_=E3=81=82_string?="
  217. 1 def Encodings.q_value_encode(encoded_str, encoding = nil)
  218. return encoded_str if encoded_str.to_s.ascii_only?
  219. string, encoding = RubyVer.q_value_encode(encoded_str, encoding)
  220. string.gsub!("=\r\n", '') # We already have limited the string to the length we want
  221. map_lines(string) do |str|
  222. "=?#{encoding}?Q?#{str.chomp.gsub(/ /, '_')}?="
  223. end.join(" ")
  224. end
  225. 1 private
  226. # Decodes a Base64 string from the "=?UTF-8?B?VGhpcyBpcyDjgYIgc3RyaW5n?=" format
  227. #
  228. # Example:
  229. #
  230. # Encodings.b_value_decode("=?UTF-8?B?VGhpcyBpcyDjgYIgc3RyaW5n?=")
  231. # #=> 'This is ��� string'
  232. 1 def Encodings.b_value_decode(str)
  233. RubyVer.b_value_decode(str)
  234. end
  235. # Decodes a Quoted-Printable string from the "=?UTF-8?Q?This_is_=E3=81=82_string?=" format
  236. #
  237. # Example:
  238. #
  239. # Encodings.q_value_decode("=?UTF-8?Q?This_is_=E3=81=82_string?=")
  240. # #=> 'This is ��� string'
  241. 1 def Encodings.q_value_decode(str)
  242. RubyVer.q_value_decode(str)
  243. end
  244. 1 def Encodings.find_encoding(str)
  245. RUBY_VERSION >= '1.9' ? str.encoding : $KCODE
  246. end
  247. # Gets the encoding type (Q or B) from the string.
  248. 1 def Encodings.value_encoding_from_string(str)
  249. str[ENCODED_VALUE, 1]
  250. end
  251. # Split header line into proper encoded and unencoded parts.
  252. #
  253. # String has to be of the format =?<encoding>?[QB]?<string>?=
  254. #
  255. # Omit unencoded space after an encoded-word.
  256. 1 def Encodings.collapse_adjacent_encodings(str)
  257. results = []
  258. last_encoded = nil # Track whether to preserve or drop whitespace
  259. lines = str.split(FULL_ENCODED_VALUE)
  260. lines.each_slice(2) do |unencoded, encoded|
  261. if last_encoded = encoded
  262. if !Utilities.blank?(unencoded) || (!last_encoded && unencoded != EMPTY)
  263. results << unencoded
  264. end
  265. results << encoded
  266. else
  267. results << unencoded
  268. end
  269. end
  270. results
  271. end
  272. # Partition the string into bounded-size chunks without splitting
  273. # multibyte characters.
  274. 1 def Encodings.each_base64_chunk_byterange(str, max_bytesize_per_base64_chunk, &block)
  275. raise "size per chunk must be multiple of 4" if (max_bytesize_per_base64_chunk % 4).nonzero?
  276. if block_given?
  277. max_bytesize = ((3 * max_bytesize_per_base64_chunk) / 4.0).floor
  278. each_chunk_byterange(str, max_bytesize, &block)
  279. else
  280. enum_for :each_base64_chunk_byterange, str, max_bytesize_per_base64_chunk
  281. end
  282. end
  283. # Partition the string into bounded-size chunks without splitting
  284. # multibyte characters.
  285. 1 def Encodings.each_chunk_byterange(str, max_bytesize_per_chunk)
  286. return enum_for(:each_chunk_byterange, str, max_bytesize_per_chunk) unless block_given?
  287. offset = 0
  288. chunksize = 0
  289. str.each_char do |chr|
  290. charsize = chr.bytesize
  291. if chunksize + charsize > max_bytesize_per_chunk
  292. yield RubyVer.string_byteslice(str, offset, chunksize)
  293. offset += chunksize
  294. chunksize = charsize
  295. else
  296. chunksize += charsize
  297. end
  298. end
  299. yield RubyVer.string_byteslice(str, offset, chunksize)
  300. end
  301. end
  302. end

target/rubygems/gems/mail-2.7.1/lib/mail/encodings/7bit.rb

81.82% lines covered

11 relevant lines. 9 lines covered and 2 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. 1 require 'mail/encodings/8bit'
  4. 1 module Mail
  5. 1 module Encodings
  6. # 7bit and 8bit are equivalent. 7bit encoding is for text only.
  7. 1 class SevenBit < EightBit
  8. 1 NAME = '7bit'
  9. 1 PRIORITY = 1
  10. 1 Encodings.register(NAME, self)
  11. 1 def self.decode(str)
  12. ::Mail::Utilities.binary_unsafe_to_lf str
  13. end
  14. 1 def self.encode(str)
  15. ::Mail::Utilities.binary_unsafe_to_crlf str
  16. end
  17. end
  18. end
  19. end

target/rubygems/gems/mail-2.7.1/lib/mail/encodings/8bit.rb

88.89% lines covered

9 relevant lines. 8 lines covered and 1 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. 1 require 'mail/encodings/binary'
  4. 1 module Mail
  5. 1 module Encodings
  6. 1 class EightBit < Binary
  7. 1 NAME = '8bit'
  8. 1 PRIORITY = 4
  9. 1 Encodings.register(NAME, self)
  10. # Per RFC 2821 4.5.3.1, SMTP lines may not be longer than 1000 octets including the <CRLF>.
  11. 1 def self.compatible_input?(str)
  12. !str.lines.find { |line| line.bytesize > 998 }
  13. end
  14. end
  15. end
  16. end

target/rubygems/gems/mail-2.7.1/lib/mail/encodings/base64.rb

70.59% lines covered

17 relevant lines. 12 lines covered and 5 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. 1 require 'mail/encodings/7bit'
  4. 1 module Mail
  5. 1 module Encodings
  6. # Base64 encoding handles binary content at the cost of 4 output bytes
  7. # per input byte.
  8. 1 class Base64 < SevenBit
  9. 1 NAME = 'base64'
  10. 1 PRIORITY = 3
  11. 1 Encodings.register(NAME, self)
  12. 1 def self.can_encode?(enc)
  13. true
  14. end
  15. 1 def self.decode(str)
  16. RubyVer.decode_base64(str)
  17. end
  18. 1 def self.encode(str)
  19. ::Mail::Utilities.binary_unsafe_to_crlf(RubyVer.encode_base64(str))
  20. end
  21. # 3 bytes in -> 4 bytes out
  22. 1 def self.cost(str)
  23. 4.0 / 3
  24. end
  25. # Ruby Base64 inserts newlines automatically, so it doesn't exceed
  26. # SMTP line length limits.
  27. 1 def self.compatible_input?(str)
  28. true
  29. end
  30. end
  31. end
  32. end

target/rubygems/gems/mail-2.7.1/lib/mail/encodings/binary.rb

100.0% lines covered

7 relevant lines. 7 lines covered and 0 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. 1 require 'mail/encodings/identity'
  4. 1 module Mail
  5. 1 module Encodings
  6. 1 class Binary < Identity
  7. 1 NAME = 'binary'
  8. 1 PRIORITY = 5
  9. 1 Encodings.register(NAME, self)
  10. end
  11. end
  12. end

target/rubygems/gems/mail-2.7.1/lib/mail/encodings/identity.rb

70.0% lines covered

10 relevant lines. 7 lines covered and 3 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. 1 require 'mail/encodings/transfer_encoding'
  4. 1 module Mail
  5. 1 module Encodings
  6. # Identity encodings do no encoding/decoding and have a fixed cost:
  7. # 1 byte in -> 1 byte out.
  8. 1 class Identity < TransferEncoding #:nodoc:
  9. 1 def self.decode(str)
  10. str
  11. end
  12. 1 def self.encode(str)
  13. str
  14. end
  15. # 1 output byte per input byte.
  16. 1 def self.cost(str)
  17. 1.0
  18. end
  19. end
  20. end
  21. end

target/rubygems/gems/mail-2.7.1/lib/mail/encodings/quoted_printable.rb

65.0% lines covered

20 relevant lines. 13 lines covered and 7 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. 1 require 'mail/encodings/7bit'
  4. 1 module Mail
  5. 1 module Encodings
  6. 1 class QuotedPrintable < SevenBit
  7. 1 NAME='quoted-printable'
  8. 1 PRIORITY = 2
  9. 1 def self.can_encode?(enc)
  10. EightBit.can_encode? enc
  11. end
  12. # Decode the string from Quoted-Printable. Cope with hard line breaks
  13. # that were incorrectly encoded as hex instead of literal CRLF.
  14. 1 def self.decode(str)
  15. str.gsub(/(?:=0D=0A|=0D|=0A)\r\n/, "\r\n").unpack("M*").first
  16. end
  17. 1 def self.encode(str)
  18. [str].pack("M")
  19. end
  20. 1 def self.cost(str)
  21. # These bytes probably do not need encoding
  22. c = str.count("\x9\xA\xD\x20-\x3C\x3E-\x7E")
  23. # Everything else turns into =XX where XX is a
  24. # two digit hex number (taking 3 bytes)
  25. total = (str.bytesize - c)*3 + c
  26. total.to_f/str.bytesize
  27. end
  28. # QP inserts newlines automatically and cannot violate the SMTP spec.
  29. 1 def self.compatible_input?(str)
  30. true
  31. end
  32. 1 private
  33. 1 Encodings.register(NAME, self)
  34. end
  35. end
  36. end

target/rubygems/gems/mail-2.7.1/lib/mail/encodings/transfer_encoding.rb

37.14% lines covered

35 relevant lines. 13 lines covered and 22 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. 1 module Mail
  4. 1 module Encodings
  5. 1 class TransferEncoding
  6. 1 NAME = ''
  7. 1 PRIORITY = -1
  8. # And encoding's superclass can always transport it since the
  9. # class hierarchy is arranged e.g. Base64 < 7bit < 8bit < Binary.
  10. 1 def self.can_transport?(enc)
  11. enc && enc <= self
  12. end
  13. # Override in subclasses to indicate that they can encode text
  14. # that couldn't be directly transported, e.g. Base64 has 7bit output,
  15. # but it can encode binary.
  16. 1 def self.can_encode?(enc)
  17. can_transport? enc
  18. end
  19. 1 def self.cost(str)
  20. raise "Unimplemented"
  21. end
  22. 1 def self.compatible_input?(str)
  23. true
  24. end
  25. 1 def self.to_s
  26. self::NAME
  27. end
  28. 1 def self.negotiate(message_encoding, source_encoding, str, allowed_encodings = nil)
  29. message_encoding = Encodings.get_encoding(message_encoding) || Encodings.get_encoding('8bit')
  30. source_encoding = Encodings.get_encoding(source_encoding)
  31. if message_encoding && source_encoding && message_encoding.can_transport?(source_encoding) && source_encoding.compatible_input?(str)
  32. source_encoding
  33. else
  34. renegotiate(message_encoding, source_encoding, str, allowed_encodings)
  35. end
  36. end
  37. 1 def self.renegotiate(message_encoding, source_encoding, str, allowed_encodings = nil)
  38. encodings = Encodings.get_all.select do |enc|
  39. (allowed_encodings.nil? || allowed_encodings.include?(enc)) &&
  40. message_encoding.can_transport?(enc) &&
  41. enc.can_encode?(source_encoding)
  42. end
  43. lowest_cost(str, encodings)
  44. end
  45. 1 def self.lowest_cost(str, encodings)
  46. best = nil
  47. best_cost = nil
  48. encodings.each do |enc|
  49. # If the current choice cannot be transported safely, give priority
  50. # to other choices but allow it to be used as a fallback.
  51. this_cost = enc.cost(str) if enc.compatible_input?(str)
  52. if !best_cost || (this_cost && this_cost < best_cost)
  53. best_cost = this_cost
  54. best = enc
  55. elsif this_cost == best_cost
  56. best = enc if enc::PRIORITY < best::PRIORITY
  57. end
  58. end
  59. best
  60. end
  61. end
  62. end
  63. end

target/rubygems/gems/mail-2.7.1/lib/mail/encodings/unix_to_unix.rb

81.82% lines covered

11 relevant lines. 9 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Mail
  3. 1 module Encodings
  4. 1 class UnixToUnix < TransferEncoding
  5. 1 NAME = "x-uuencode"
  6. 1 def self.decode(str)
  7. str.sub(/\Abegin \d+ [^\n]*\n/, '').unpack('u').first
  8. end
  9. 1 def self.encode(str)
  10. [str].pack("u")
  11. end
  12. 1 Encodings.register(NAME, self)
  13. 1 Encodings.register("uuencode", self)
  14. 1 Encodings.register("x-uue", self)
  15. end
  16. end
  17. end

target/rubygems/gems/mail-2.7.1/lib/mail/envelope.rb

60.0% lines covered

10 relevant lines. 6 lines covered and 4 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. #
  4. # = Mail Envelope
  5. #
  6. # The Envelope class provides a field for the first line in an
  7. # mbox file, that looks like "From mikel@test.lindsaar.net DATETIME"
  8. #
  9. # This envelope class reads that line, and turns it into an
  10. # Envelope.from and Envelope.date for your use.
  11. 1 module Mail
  12. 1 class Envelope < StructuredField
  13. 1 def initialize(*args)
  14. super(FIELD_NAME, args.last.to_s)
  15. end
  16. 1 def element
  17. @element ||= Mail::EnvelopeFromElement.new(value)
  18. end
  19. 1 def date
  20. ::DateTime.parse("#{element.date_time}")
  21. end
  22. 1 def from
  23. element.address
  24. end
  25. end
  26. end

target/rubygems/gems/mail-2.7.1/lib/mail/field.rb

43.86% lines covered

114 relevant lines. 50 lines covered and 64 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require 'mail/fields'
  3. 1 require 'mail/constants'
  4. # encoding: utf-8
  5. 1 module Mail
  6. # Provides a single class to call to create a new structured or unstructured
  7. # field. Works out per RFC what field of field it is being given and returns
  8. # the correct field of class back on new.
  9. #
  10. # ===Per RFC 2822
  11. #
  12. # 2.2. Header Fields
  13. #
  14. # Header fields are lines composed of a field name, followed by a colon
  15. # (":"), followed by a field body, and terminated by CRLF. A field
  16. # name MUST be composed of printable US-ASCII characters (i.e.,
  17. # characters that have values between 33 and 126, inclusive), except
  18. # colon. A field body may be composed of any US-ASCII characters,
  19. # except for CR and LF. However, a field body may contain CRLF when
  20. # used in header "folding" and "unfolding" as described in section
  21. # 2.2.3. All field bodies MUST conform to the syntax described in
  22. # sections 3 and 4 of this standard.
  23. #
  24. 1 class Field
  25. 1 include Utilities
  26. 1 include Comparable
  27. 1 STRUCTURED_FIELDS = %w[ bcc cc content-description content-disposition
  28. content-id content-location content-transfer-encoding
  29. content-type date from in-reply-to keywords message-id
  30. mime-version received references reply-to
  31. resent-bcc resent-cc resent-date resent-from
  32. resent-message-id resent-sender resent-to
  33. return-path sender to ]
  34. 1 KNOWN_FIELDS = STRUCTURED_FIELDS + ['comments', 'subject']
  35. 1 FIELDS_MAP = {
  36. "to" => ToField,
  37. "cc" => CcField,
  38. "bcc" => BccField,
  39. "message-id" => MessageIdField,
  40. "in-reply-to" => InReplyToField,
  41. "references" => ReferencesField,
  42. "subject" => SubjectField,
  43. "comments" => CommentsField,
  44. "keywords" => KeywordsField,
  45. "date" => DateField,
  46. "from" => FromField,
  47. "sender" => SenderField,
  48. "reply-to" => ReplyToField,
  49. "resent-date" => ResentDateField,
  50. "resent-from" => ResentFromField,
  51. "resent-sender" => ResentSenderField,
  52. "resent-to" => ResentToField,
  53. "resent-cc" => ResentCcField,
  54. "resent-bcc" => ResentBccField,
  55. "resent-message-id" => ResentMessageIdField,
  56. "return-path" => ReturnPathField,
  57. "received" => ReceivedField,
  58. "mime-version" => MimeVersionField,
  59. "content-transfer-encoding" => ContentTransferEncodingField,
  60. "content-description" => ContentDescriptionField,
  61. "content-disposition" => ContentDispositionField,
  62. "content-type" => ContentTypeField,
  63. "content-id" => ContentIdField,
  64. "content-location" => ContentLocationField,
  65. }
  66. 1 FIELD_NAME_MAP = FIELDS_MAP.inject({}) do |map, (field, field_klass)|
  67. 29 map.update(field => field_klass::CAPITALIZED_FIELD)
  68. end
  69. # Generic Field Exception
  70. 1 class FieldError < StandardError
  71. end
  72. # Raised when a parsing error has occurred (ie, a StructuredField has tried
  73. # to parse a field that is invalid or improperly written)
  74. 1 class ParseError < FieldError #:nodoc:
  75. 1 attr_accessor :element, :value, :reason
  76. 1 def initialize(element, value, reason)
  77. @element = element
  78. @value = to_utf8(value)
  79. @reason = to_utf8(reason)
  80. super("#{@element} can not parse |#{@value}|: #{@reason}")
  81. end
  82. 1 private
  83. 1 def to_utf8(text)
  84. if text.respond_to?(:force_encoding)
  85. text.dup.force_encoding(Encoding::UTF_8)
  86. else
  87. text
  88. end
  89. end
  90. end
  91. 1 class NilParseError < ParseError #:nodoc:
  92. 1 def initialize(element)
  93. super element, nil, 'nil is invalid'
  94. end
  95. end
  96. 1 class IncompleteParseError < ParseError #:nodoc:
  97. 1 def initialize(element, original_text, unparsed_index)
  98. parsed_text = to_utf8(original_text[0...unparsed_index])
  99. super element, original_text, "Only able to parse up to #{parsed_text.inspect}"
  100. end
  101. end
  102. # Raised when attempting to set a structured field's contents to an invalid syntax
  103. 1 class SyntaxError < FieldError #:nodoc:
  104. end
  105. 1 class << self
  106. # Parse a field from a raw header line:
  107. #
  108. # Mail::Field.parse("field-name: field data")
  109. # # => #<Mail::Field ���>
  110. 1 def parse(field, charset = nil)
  111. name, value = split(field)
  112. if name && value
  113. new name, value, charset
  114. end
  115. end
  116. 1 def split(raw_field) #:nodoc:
  117. if raw_field.index(Constants::COLON)
  118. name, value = raw_field.split(Constants::COLON, 2)
  119. name.rstrip!
  120. if name =~ /\A#{Constants::FIELD_NAME}\z/
  121. [ name.rstrip, value.strip ]
  122. else
  123. Kernel.warn "WARNING: Ignoring unparsable header #{raw_field.inspect}: invalid header name syntax: #{name.inspect}"
  124. nil
  125. end
  126. else
  127. raw_field.strip
  128. end
  129. rescue => error
  130. warn "WARNING: Ignoring unparsable header #{raw_field.inspect}: #{error.class}: #{error.message}"
  131. nil
  132. end
  133. end
  134. 1 attr_reader :unparsed_value
  135. # Create a field by name and optional value:
  136. #
  137. # Mail::Field.new("field-name", "value")
  138. # # => #<Mail::Field ���>
  139. #
  140. # Values that aren't strings or arrays are coerced to Strings with `#to_s`.
  141. #
  142. # Mail::Field.new("field-name", 1234)
  143. # # => #<Mail::Field ���>
  144. #
  145. # Mail::Field.new('content-type', ['text', 'plain', {:charset => 'UTF-8'}])
  146. # # => #<Mail::Field ���>
  147. 1 def initialize(name, value = nil, charset = 'utf-8')
  148. case
  149. when name.index(COLON)
  150. Kernel.warn 'Passing an unparsed header field to Mail::Field.new is deprecated and will be removed in Mail 2.8.0. Use Mail::Field.parse instead.'
  151. @name, @unparsed_value = self.class.split(name)
  152. @charset = Utilities.blank?(value) ? charset : value
  153. when Utilities.blank?(value)
  154. @name = name
  155. @unparsed_value = nil
  156. @charset = charset
  157. else
  158. @name = name
  159. @unparsed_value = value
  160. @charset = charset
  161. end
  162. @name = FIELD_NAME_MAP[@name.to_s.downcase] || @name
  163. end
  164. 1 def field=(value)
  165. @field = value
  166. end
  167. 1 def field
  168. @field ||= create_field(@name, @unparsed_value, @charset)
  169. end
  170. 1 def name
  171. @name
  172. end
  173. 1 def value
  174. field.value
  175. end
  176. 1 def value=(val)
  177. @field = create_field(name, val, @charset)
  178. end
  179. 1 def to_s
  180. field.to_s
  181. end
  182. 1 def inspect
  183. "#<#{self.class.name} 0x#{(object_id * 2).to_s(16)} #{instance_variables.map do |ivar|
  184. "#{ivar}=#{instance_variable_get(ivar).inspect}"
  185. end.join(" ")}>"
  186. end
  187. 1 def update(name, value)
  188. @field = create_field(name, value, @charset)
  189. end
  190. 1 def same( other )
  191. return false unless other.kind_of?(self.class)
  192. match_to_s(other.name, self.name)
  193. end
  194. 1 def ==( other )
  195. return false unless other.kind_of?(self.class)
  196. match_to_s(other.name, self.name) && match_to_s(other.value, self.value)
  197. end
  198. 1 def responsible_for?( val )
  199. name.to_s.casecmp(val.to_s) == 0
  200. end
  201. 1 def <=>( other )
  202. self.field_order_id <=> other.field_order_id
  203. end
  204. 1 def field_order_id
  205. @field_order_id ||= (FIELD_ORDER_LOOKUP[self.name.to_s.downcase] || 100)
  206. end
  207. 1 def method_missing(name, *args, &block)
  208. field.send(name, *args, &block)
  209. end
  210. 1 if RUBY_VERSION >= '1.9.2'
  211. 1 def respond_to_missing?(method_name, include_private)
  212. field.respond_to?(method_name, include_private) || super
  213. end
  214. else
  215. def respond_to?(method_name, include_private = false)
  216. field.respond_to?(method_name, include_private) || super
  217. end
  218. end
  219. 1 FIELD_ORDER = %w[ return-path received
  220. resent-date resent-from resent-sender resent-to
  221. resent-cc resent-bcc resent-message-id
  222. date from sender reply-to to cc bcc
  223. message-id in-reply-to references
  224. subject comments keywords
  225. mime-version content-type content-transfer-encoding
  226. content-location content-disposition content-description ]
  227. 1 FIELD_ORDER_LOOKUP = Hash[FIELD_ORDER.each_with_index.to_a]
  228. 1 private
  229. 1 def create_field(name, value, charset)
  230. new_field(name, value, charset)
  231. rescue Mail::Field::ParseError => e
  232. field = Mail::UnstructuredField.new(name, value)
  233. field.errors << [name, value, e]
  234. field
  235. end
  236. 1 def new_field(name, value, charset)
  237. value = unfold(value) if value.is_a?(String)
  238. if klass = field_class_for(name)
  239. klass.new(value, charset)
  240. else
  241. OptionalField.new(name, value, charset)
  242. end
  243. end
  244. 1 def field_class_for(name)
  245. FIELDS_MAP[name.to_s.downcase]
  246. end
  247. # 2.2.3. Long Header Fields
  248. #
  249. # The process of moving from this folded multiple-line representation
  250. # of a header field to its single line representation is called
  251. # "unfolding". Unfolding is accomplished by simply removing any CRLF
  252. # that is immediately followed by WSP. Each header field should be
  253. # treated in its unfolded form for further syntactic and semantic
  254. # evaluation.
  255. 1 def unfold(string)
  256. string.gsub(/#{Constants::CRLF}(#{Constants::WSP})/m, '\1')
  257. end
  258. end
  259. end

target/rubygems/gems/mail-2.7.1/lib/mail/field_list.rb

33.33% lines covered

12 relevant lines. 4 lines covered and 8 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. 1 module Mail
  4. # Field List class provides an enhanced array that keeps a list of
  5. # email fields in order. And allows you to insert new fields without
  6. # having to worry about the order they will appear in.
  7. 1 class FieldList < Array
  8. 1 include Enumerable
  9. # Insert the field in sorted order.
  10. #
  11. # Heavily based on bisect.insort from Python, which is:
  12. # Copyright (C) 2001-2013 Python Software Foundation.
  13. # Licensed under <http://docs.python.org/license.html>
  14. # From <http://hg.python.org/cpython/file/2.7/Lib/bisect.py>
  15. 1 def <<( new_field )
  16. lo = 0
  17. hi = size
  18. while lo < hi
  19. mid = (lo + hi).div(2)
  20. if new_field < self[mid]
  21. hi = mid
  22. else
  23. lo = mid + 1
  24. end
  25. end
  26. insert(lo, new_field)
  27. end
  28. end
  29. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields.rb

100.0% lines covered

33 relevant lines. 33 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Mail
  3. 1 register_autoload :UnstructuredField, 'mail/fields/unstructured_field'
  4. 1 register_autoload :StructuredField, 'mail/fields/structured_field'
  5. 1 register_autoload :OptionalField, 'mail/fields/optional_field'
  6. 1 register_autoload :BccField, 'mail/fields/bcc_field'
  7. 1 register_autoload :CcField, 'mail/fields/cc_field'
  8. 1 register_autoload :CommentsField, 'mail/fields/comments_field'
  9. 1 register_autoload :ContentDescriptionField, 'mail/fields/content_description_field'
  10. 1 register_autoload :ContentDispositionField, 'mail/fields/content_disposition_field'
  11. 1 register_autoload :ContentIdField, 'mail/fields/content_id_field'
  12. 1 register_autoload :ContentLocationField, 'mail/fields/content_location_field'
  13. 1 register_autoload :ContentTransferEncodingField, 'mail/fields/content_transfer_encoding_field'
  14. 1 register_autoload :ContentTypeField, 'mail/fields/content_type_field'
  15. 1 register_autoload :DateField, 'mail/fields/date_field'
  16. 1 register_autoload :FromField, 'mail/fields/from_field'
  17. 1 register_autoload :InReplyToField, 'mail/fields/in_reply_to_field'
  18. 1 register_autoload :KeywordsField, 'mail/fields/keywords_field'
  19. 1 register_autoload :MessageIdField, 'mail/fields/message_id_field'
  20. 1 register_autoload :MimeVersionField, 'mail/fields/mime_version_field'
  21. 1 register_autoload :ReceivedField, 'mail/fields/received_field'
  22. 1 register_autoload :ReferencesField, 'mail/fields/references_field'
  23. 1 register_autoload :ReplyToField, 'mail/fields/reply_to_field'
  24. 1 register_autoload :ResentBccField, 'mail/fields/resent_bcc_field'
  25. 1 register_autoload :ResentCcField, 'mail/fields/resent_cc_field'
  26. 1 register_autoload :ResentDateField, 'mail/fields/resent_date_field'
  27. 1 register_autoload :ResentFromField, 'mail/fields/resent_from_field'
  28. 1 register_autoload :ResentMessageIdField, 'mail/fields/resent_message_id_field'
  29. 1 register_autoload :ResentSenderField, 'mail/fields/resent_sender_field'
  30. 1 register_autoload :ResentToField, 'mail/fields/resent_to_field'
  31. 1 register_autoload :ReturnPathField, 'mail/fields/return_path_field'
  32. 1 register_autoload :SenderField, 'mail/fields/sender_field'
  33. 1 register_autoload :SubjectField, 'mail/fields/subject_field'
  34. 1 register_autoload :ToField, 'mail/fields/to_field'
  35. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/bcc_field.rb

55.0% lines covered

20 relevant lines. 11 lines covered and 9 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. #
  4. # = Blind Carbon Copy Field
  5. #
  6. # The Bcc field inherits from StructuredField and handles the Bcc: header
  7. # field in the email.
  8. #
  9. # Sending bcc to a mail message will instantiate a Mail::Field object that
  10. # has a BccField as its field type. This includes all Mail::CommonAddress
  11. # module instance metods.
  12. #
  13. # Only one Bcc field can appear in a header, though it can have multiple
  14. # addresses and groups of addresses.
  15. #
  16. # == Examples:
  17. #
  18. # mail = Mail.new
  19. # mail.bcc = 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  20. # mail.bcc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  21. # mail[:bcc] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::BccField:0x180e1c4
  22. # mail['bcc'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::BccField:0x180e1c4
  23. # mail['Bcc'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::BccField:0x180e1c4
  24. #
  25. # mail[:bcc].encoded #=> '' # Bcc field does not get output into an email
  26. # mail[:bcc].decoded #=> 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  27. # mail[:bcc].addresses #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  28. # mail[:bcc].formatted #=> ['Mikel Lindsaar <mikel@test.lindsaar.net>', 'ada@test.lindsaar.net']
  29. #
  30. 1 require 'mail/fields/common/common_address'
  31. 1 module Mail
  32. 1 class BccField < StructuredField
  33. 1 include Mail::CommonAddress
  34. 1 FIELD_NAME = 'bcc'
  35. 1 CAPITALIZED_FIELD = 'Bcc'
  36. 1 def initialize(value = nil, charset = 'utf-8')
  37. @charset = charset
  38. super(CAPITALIZED_FIELD, value, charset)
  39. self
  40. end
  41. 1 def include_in_headers=(include_in_headers)
  42. @include_in_headers = include_in_headers
  43. end
  44. 1 def include_in_headers
  45. defined?(@include_in_headers) ? @include_in_headers : self.include_in_headers = false
  46. end
  47. # Bcc field should not be :encoded by default
  48. 1 def encoded
  49. if include_in_headers
  50. do_encode(CAPITALIZED_FIELD)
  51. else
  52. ''
  53. end
  54. end
  55. 1 def decoded
  56. do_decode
  57. end
  58. end
  59. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/cc_field.rb

64.29% lines covered

14 relevant lines. 9 lines covered and 5 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. #
  4. # = Carbon Copy Field
  5. #
  6. # The Cc field inherits from StructuredField and handles the Cc: header
  7. # field in the email.
  8. #
  9. # Sending cc to a mail message will instantiate a Mail::Field object that
  10. # has a CcField as its field type. This includes all Mail::CommonAddress
  11. # module instance metods.
  12. #
  13. # Only one Cc field can appear in a header, though it can have multiple
  14. # addresses and groups of addresses.
  15. #
  16. # == Examples:
  17. #
  18. # mail = Mail.new
  19. # mail.cc = 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  20. # mail.cc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  21. # mail[:cc] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::CcField:0x180e1c4
  22. # mail['cc'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::CcField:0x180e1c4
  23. # mail['Cc'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::CcField:0x180e1c4
  24. #
  25. # mail[:cc].encoded #=> 'Cc: Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net\r\n'
  26. # mail[:cc].decoded #=> 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  27. # mail[:cc].addresses #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  28. # mail[:cc].formatted #=> ['Mikel Lindsaar <mikel@test.lindsaar.net>', 'ada@test.lindsaar.net']
  29. #
  30. 1 require 'mail/fields/common/common_address'
  31. 1 module Mail
  32. 1 class CcField < StructuredField
  33. 1 include Mail::CommonAddress
  34. 1 FIELD_NAME = 'cc'
  35. 1 CAPITALIZED_FIELD = 'Cc'
  36. 1 def initialize(value = nil, charset = 'utf-8')
  37. self.charset = charset
  38. super(CAPITALIZED_FIELD, value, charset)
  39. self
  40. end
  41. 1 def encoded
  42. do_encode(CAPITALIZED_FIELD)
  43. end
  44. 1 def decoded
  45. do_decode
  46. end
  47. end
  48. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/comments_field.rb

55.56% lines covered

9 relevant lines. 5 lines covered and 4 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. #
  4. # = Comments Field
  5. #
  6. # The Comments field inherits from UnstructuredField and handles the Comments:
  7. # header field in the email.
  8. #
  9. # Sending comments to a mail message will instantiate a Mail::Field object that
  10. # has a CommentsField as its field type.
  11. #
  12. # An email header can have as many comments fields as it wants. There is no upper
  13. # limit, the comments field is also optional (that is, no comment is needed)
  14. #
  15. # == Examples:
  16. #
  17. # mail = Mail.new
  18. # mail.comments = 'This is a comment'
  19. # mail.comments #=> 'This is a comment'
  20. # mail[:comments] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::CommentsField:0x180e1c4
  21. # mail['comments'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::CommentsField:0x180e1c4
  22. # mail['comments'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::CommentsField:0x180e1c4
  23. #
  24. # mail.comments = "This is another comment"
  25. # mail[:comments].map { |c| c.to_s }
  26. # #=> ['This is a comment', "This is another comment"]
  27. #
  28. 1 module Mail
  29. 1 class CommentsField < UnstructuredField
  30. 1 FIELD_NAME = 'comments'
  31. 1 CAPITALIZED_FIELD = 'Comments'
  32. 1 def initialize(value = nil, charset = 'utf-8')
  33. @charset = charset
  34. super(CAPITALIZED_FIELD, value)
  35. self.parse
  36. self
  37. end
  38. end
  39. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/common/address_container.rb

57.14% lines covered

7 relevant lines. 4 lines covered and 3 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Mail
  3. 1 class AddressContainer < Array
  4. 1 def initialize(field, list = [])
  5. @field = field
  6. super(list)
  7. end
  8. 1 def <<(address)
  9. @field << address
  10. end
  11. end
  12. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/common/common_address.rb

31.71% lines covered

82 relevant lines. 26 lines covered and 56 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. 1 require 'mail/fields/common/address_container'
  4. 1 module Mail
  5. 1 module CommonAddress # :nodoc:
  6. 1 def parse(val = value)
  7. unless Utilities.blank?(val)
  8. @address_list = AddressList.new(encode_if_needed(val))
  9. else
  10. nil
  11. end
  12. end
  13. 1 def charset
  14. @charset
  15. end
  16. 1 def encode_if_needed(val) #:nodoc:
  17. # Need to join arrays of addresses into a single value
  18. if val.kind_of?(Array)
  19. val.compact.map { |a| encode_if_needed a }.join(', ')
  20. # Pass through UTF-8; encode non-UTF-8.
  21. else
  22. utf8_if_needed(val) || Encodings.encode_non_usascii(val, charset)
  23. end
  24. end
  25. # Allows you to iterate through each address object in the address_list
  26. 1 def each
  27. address_list.addresses.each do |address|
  28. yield(address)
  29. end
  30. end
  31. # Returns the address string of all the addresses in the address list
  32. 1 def addresses
  33. list = address_list.addresses.map { |a| a.address }
  34. Mail::AddressContainer.new(self, list)
  35. end
  36. # Returns the formatted string of all the addresses in the address list
  37. 1 def formatted
  38. list = address_list.addresses.map { |a| a.format }
  39. Mail::AddressContainer.new(self, list)
  40. end
  41. # Returns the display name of all the addresses in the address list
  42. 1 def display_names
  43. list = address_list.addresses.map { |a| a.display_name }
  44. Mail::AddressContainer.new(self, list)
  45. end
  46. # Returns the actual address objects in the address list
  47. 1 def addrs
  48. list = address_list.addresses
  49. Mail::AddressContainer.new(self, list)
  50. end
  51. # Returns a hash of group name => address strings for the address list
  52. 1 def groups
  53. address_list.addresses_grouped_by_group
  54. end
  55. # Returns the addresses that are part of groups
  56. 1 def group_addresses
  57. decoded_group_addresses
  58. end
  59. # Returns a list of decoded group addresses
  60. 1 def decoded_group_addresses
  61. groups.map { |k,v| v.map { |a| a.decoded } }.flatten
  62. end
  63. # Returns a list of encoded group addresses
  64. 1 def encoded_group_addresses
  65. groups.map { |k,v| v.map { |a| a.encoded } }.flatten
  66. end
  67. # Returns the name of all the groups in a string
  68. 1 def group_names # :nodoc:
  69. address_list.group_names
  70. end
  71. 1 def default
  72. addresses
  73. end
  74. 1 def <<(val)
  75. case
  76. when val.nil?
  77. raise ArgumentError, "Need to pass an address to <<"
  78. when Utilities.blank?(val)
  79. parse(encoded)
  80. else
  81. self.value = [self.value, val].reject {|a| Utilities.blank?(a) }.join(", ")
  82. end
  83. end
  84. 1 def value=(val)
  85. super
  86. parse(self.value)
  87. end
  88. 1 private
  89. 1 if 'string'.respond_to?(:encoding)
  90. # Pass through UTF-8 addresses
  91. 1 def utf8_if_needed(val)
  92. if charset =~ /\AUTF-?8\z/i
  93. val
  94. elsif val.encoding == Encoding::UTF_8
  95. val
  96. elsif (utf8 = val.dup.force_encoding(Encoding::UTF_8)).valid_encoding?
  97. utf8
  98. end
  99. end
  100. else
  101. def utf8_if_needed(val)
  102. if charset =~ /\AUTF-?8\z/i
  103. val
  104. end
  105. end
  106. end
  107. 1 def do_encode(field_name)
  108. return '' if Utilities.blank?(value)
  109. address_array = address_list.addresses.reject { |a| encoded_group_addresses.include?(a.encoded) }.compact.map { |a| a.encoded }
  110. address_text = address_array.join(", \r\n\s")
  111. group_array = groups.map { |k,v| "#{k}: #{v.map { |a| a.encoded }.join(", \r\n\s")};" }
  112. group_text = group_array.join(" \r\n\s")
  113. return_array = [address_text, group_text].reject { |a| Utilities.blank?(a) }
  114. "#{field_name}: #{return_array.join(", \r\n\s")}\r\n"
  115. end
  116. 1 def do_decode
  117. return nil if Utilities.blank?(value)
  118. address_array = address_list.addresses.reject { |a| decoded_group_addresses.include?(a.decoded) }.map { |a| a.decoded }
  119. address_text = address_array.join(", ")
  120. group_array = groups.map { |k,v| "#{k}: #{v.map { |a| a.decoded }.join(", ")};" }
  121. group_text = group_array.join(" ")
  122. return_array = [address_text, group_text].reject { |a| Utilities.blank?(a) }
  123. return_array.join(", ")
  124. end
  125. 1 def address_list # :nodoc:
  126. @address_list ||= AddressList.new(value)
  127. end
  128. 1 def get_group_addresses(group_list)
  129. if group_list.respond_to?(:addresses)
  130. group_list.addresses.map do |address|
  131. Mail::Address.new(address)
  132. end
  133. else
  134. []
  135. end
  136. end
  137. end
  138. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/common/common_date.rb

52.94% lines covered

17 relevant lines. 9 lines covered and 8 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. 1 module Mail
  4. 1 module CommonDate # :nodoc:
  5. # Returns a date time object of the parsed date
  6. 1 def date_time
  7. ::DateTime.parse("#{element.date_string} #{element.time_string}")
  8. end
  9. 1 def default
  10. date_time
  11. end
  12. 1 def parse(val = value)
  13. unless Utilities.blank?(val)
  14. @element = Mail::DateTimeElement.new(val)
  15. else
  16. nil
  17. end
  18. end
  19. 1 private
  20. 1 def do_encode(field_name)
  21. "#{field_name}: #{value}\r\n"
  22. end
  23. 1 def do_decode
  24. "#{value}"
  25. end
  26. 1 def element
  27. @element ||= Mail::DateTimeElement.new(value)
  28. end
  29. end
  30. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/common/common_field.rb

51.85% lines covered

27 relevant lines. 14 lines covered and 13 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. 1 module Mail
  4. 1 module CommonField # :nodoc:
  5. 1 include Mail::Constants
  6. 1 def name=(value)
  7. @name = value
  8. end
  9. 1 def name
  10. @name ||= nil
  11. end
  12. 1 def value=(value)
  13. @length = nil
  14. @element = nil
  15. @value = value.is_a?(Array) ? value : value.to_s
  16. end
  17. 1 def value
  18. @value
  19. end
  20. 1 def to_s
  21. decoded.to_s
  22. end
  23. 1 def default
  24. decoded
  25. end
  26. 1 def field_length
  27. @length ||= "#{name}: #{encode(decoded)}".length
  28. end
  29. 1 def responsible_for?( val )
  30. name.to_s.casecmp(val.to_s) == 0
  31. end
  32. 1 private
  33. 1 FILENAME_RE = /\b(filename|name)=([^;"\r\n]+\s[^;"\r\n]+)/
  34. 1 def ensure_filename_quoted(value)
  35. if value.is_a?(String)
  36. value.sub FILENAME_RE, '\1="\2"'
  37. else
  38. value
  39. end
  40. end
  41. end
  42. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/common/common_message_id.rb

45.83% lines covered

24 relevant lines. 11 lines covered and 13 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. 1 module Mail
  4. 1 module CommonMessageId # :nodoc:
  5. 1 def element
  6. @element ||= Mail::MessageIdsElement.new(value) unless Utilities.blank?(value)
  7. end
  8. 1 def parse(val = value)
  9. unless Utilities.blank?(val)
  10. @element = Mail::MessageIdsElement.new(val)
  11. else
  12. nil
  13. end
  14. end
  15. 1 def message_id
  16. element.message_id if element
  17. end
  18. 1 def message_ids
  19. element.message_ids if element
  20. end
  21. 1 def default
  22. return nil unless message_ids
  23. if message_ids.length == 1
  24. message_ids[0]
  25. else
  26. message_ids
  27. end
  28. end
  29. 1 private
  30. 1 def do_encode(field_name)
  31. %Q{#{field_name}: #{formated_message_ids("\r\n ")}\r\n}
  32. end
  33. 1 def do_decode
  34. formated_message_ids(' ')
  35. end
  36. 1 def formated_message_ids(join)
  37. message_ids.map{ |m| "<#{m}>" }.join(join) if message_ids
  38. end
  39. end
  40. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/common/parameter_hash.rb

20.69% lines covered

29 relevant lines. 6 lines covered and 23 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. 1 module Mail
  4. # ParameterHash is an intelligent Hash that allows you to add
  5. # parameter values including the MIME extension paramaters that
  6. # have the name*0="blah", name*1="bleh" keys, and will just return
  7. # a single key called name="blahbleh" and do any required un-encoding
  8. # to make that happen
  9. # Parameters are defined in RFC2045, split keys are in RFC2231
  10. 1 class ParameterHash < IndifferentHash
  11. 1 include Mail::Utilities
  12. 1 def [](key_name)
  13. key_pattern = Regexp.escape(key_name.to_s)
  14. pairs = []
  15. exact = nil
  16. each do |k,v|
  17. if k =~ /^#{key_pattern}(\*|$)/i
  18. if $1 == ASTERISK
  19. pairs << [k, v]
  20. else
  21. exact = k
  22. end
  23. end
  24. end
  25. if pairs.empty? # Just dealing with a single value pair
  26. super(exact || key_name)
  27. else # Dealing with a multiple value pair or a single encoded value pair
  28. string = pairs.sort { |a,b| a.first.to_s <=> b.first.to_s }.map { |v| v.last }.join('')
  29. if mt = string.match(/([\w\-]+)?'(\w\w)?'(.*)/)
  30. string = mt[3]
  31. encoding = mt[1]
  32. else
  33. encoding = nil
  34. end
  35. Mail::Encodings.param_decode(string, encoding)
  36. end
  37. end
  38. 1 def encoded
  39. map.sort_by { |a| a.first.to_s }.map! do |key_name, value|
  40. unless value.ascii_only?
  41. value = Mail::Encodings.param_encode(value)
  42. key_name = "#{key_name}*"
  43. end
  44. %Q{#{key_name}=#{quote_token(value)}}
  45. end.join(";\r\n\s")
  46. end
  47. 1 def decoded
  48. map.sort_by { |a| a.first.to_s }.map! do |key_name, value|
  49. %Q{#{key_name}=#{quote_token(value)}}
  50. end.join("; ")
  51. end
  52. end
  53. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/content_description_field.rb

55.56% lines covered

9 relevant lines. 5 lines covered and 4 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. #
  4. #
  5. #
  6. 1 module Mail
  7. 1 class ContentDescriptionField < UnstructuredField
  8. 1 FIELD_NAME = 'content-description'
  9. 1 CAPITALIZED_FIELD = 'Content-Description'
  10. 1 def initialize(value = nil, charset = 'utf-8')
  11. self.charset = charset
  12. super(CAPITALIZED_FIELD, value, charset)
  13. self.parse
  14. self
  15. end
  16. end
  17. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/content_disposition_field.rb

34.21% lines covered

38 relevant lines. 13 lines covered and 25 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. 1 require 'mail/fields/common/parameter_hash'
  4. 1 module Mail
  5. 1 class ContentDispositionField < StructuredField
  6. 1 FIELD_NAME = 'content-disposition'
  7. 1 CAPITALIZED_FIELD = 'Content-Disposition'
  8. 1 def initialize(value = nil, charset = 'utf-8')
  9. self.charset = charset
  10. value = ensure_filename_quoted(value)
  11. super(CAPITALIZED_FIELD, value, charset)
  12. self.parse
  13. self
  14. end
  15. 1 def parse(val = value)
  16. unless Utilities.blank?(val)
  17. @element = Mail::ContentDispositionElement.new(val)
  18. end
  19. end
  20. 1 def element
  21. @element ||= Mail::ContentDispositionElement.new(value)
  22. end
  23. 1 def disposition_type
  24. element.disposition_type
  25. end
  26. 1 def parameters
  27. @parameters = ParameterHash.new
  28. element.parameters.each { |p| @parameters.merge!(p) } unless element.parameters.nil?
  29. @parameters
  30. end
  31. 1 def filename
  32. case
  33. when parameters['filename']
  34. @filename = parameters['filename']
  35. when parameters['name']
  36. @filename = parameters['name']
  37. else
  38. @filename = nil
  39. end
  40. @filename
  41. end
  42. # TODO: Fix this up
  43. 1 def encoded
  44. if parameters.length > 0
  45. p = ";\r\n\s#{parameters.encoded}\r\n"
  46. else
  47. p = "\r\n"
  48. end
  49. "#{CAPITALIZED_FIELD}: #{disposition_type}" + p
  50. end
  51. 1 def decoded
  52. if parameters.length > 0
  53. p = "; #{parameters.decoded}"
  54. else
  55. p = ""
  56. end
  57. "#{disposition_type}" + p
  58. end
  59. end
  60. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/content_id_field.rb

45.16% lines covered

31 relevant lines. 14 lines covered and 17 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. #
  4. #
  5. #
  6. 1 module Mail
  7. 1 class ContentIdField < StructuredField
  8. 1 FIELD_NAME = 'content-id'
  9. 1 CAPITALIZED_FIELD = "Content-ID"
  10. 1 def initialize(value = nil, charset = 'utf-8')
  11. self.charset = charset
  12. @uniq = 1
  13. if Utilities.blank?(value)
  14. value = generate_content_id
  15. else
  16. value = value.to_s
  17. end
  18. super(CAPITALIZED_FIELD, value, charset)
  19. self.parse
  20. self
  21. end
  22. 1 def parse(val = value)
  23. unless Utilities.blank?(val)
  24. @element = Mail::MessageIdsElement.new(val)
  25. end
  26. end
  27. 1 def element
  28. @element ||= Mail::MessageIdsElement.new(value)
  29. end
  30. 1 def name
  31. 'Content-ID'
  32. end
  33. 1 def content_id
  34. element.message_id
  35. end
  36. 1 def to_s
  37. "<#{content_id}>"
  38. end
  39. # TODO: Fix this up
  40. 1 def encoded
  41. "#{CAPITALIZED_FIELD}: #{to_s}\r\n"
  42. end
  43. 1 def decoded
  44. "#{to_s}"
  45. end
  46. 1 private
  47. 1 def generate_content_id
  48. "<#{Mail.random_tag}@#{::Socket.gethostname}.mail>"
  49. end
  50. end
  51. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/content_location_field.rb

50.0% lines covered

20 relevant lines. 10 lines covered and 10 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. #
  4. #
  5. #
  6. 1 module Mail
  7. 1 class ContentLocationField < StructuredField
  8. 1 FIELD_NAME = 'content-location'
  9. 1 CAPITALIZED_FIELD = 'Content-Location'
  10. 1 def initialize(value = nil, charset = 'utf-8')
  11. self.charset = charset
  12. super(CAPITALIZED_FIELD, value, charset)
  13. self.parse
  14. self
  15. end
  16. 1 def parse(val = value)
  17. unless Utilities.blank?(val)
  18. @element = Mail::ContentLocationElement.new(val)
  19. end
  20. end
  21. 1 def element
  22. @element ||= Mail::ContentLocationElement.new(value)
  23. end
  24. 1 def location
  25. element.location
  26. end
  27. # TODO: Fix this up
  28. 1 def encoded
  29. "#{CAPITALIZED_FIELD}: #{location}\r\n"
  30. end
  31. 1 def decoded
  32. location
  33. end
  34. end
  35. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/content_transfer_encoding_field.rb

45.45% lines covered

22 relevant lines. 10 lines covered and 12 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. #
  4. #
  5. #
  6. 1 module Mail
  7. 1 class ContentTransferEncodingField < StructuredField
  8. 1 FIELD_NAME = 'content-transfer-encoding'
  9. 1 CAPITALIZED_FIELD = 'Content-Transfer-Encoding'
  10. 1 def initialize(value = nil, charset = 'utf-8')
  11. self.charset = charset
  12. value = '7bit' if value.to_s =~ /7-?bits?/i
  13. value = '8bit' if value.to_s =~ /8-?bits?/i
  14. super(CAPITALIZED_FIELD, value, charset)
  15. self.parse
  16. self
  17. end
  18. 1 def parse(val = value)
  19. unless Utilities.blank?(val)
  20. @element = Mail::ContentTransferEncodingElement.new(val)
  21. end
  22. end
  23. 1 def element
  24. @element ||= Mail::ContentTransferEncodingElement.new(value)
  25. end
  26. 1 def encoding
  27. element.encoding
  28. end
  29. # TODO: Fix this up
  30. 1 def encoded
  31. "#{CAPITALIZED_FIELD}: #{encoding}\r\n"
  32. end
  33. 1 def decoded
  34. encoding
  35. end
  36. end
  37. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/content_type_field.rb

26.26% lines covered

99 relevant lines. 26 lines covered and 73 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. 1 require 'mail/fields/common/parameter_hash'
  4. 1 module Mail
  5. 1 class ContentTypeField < StructuredField
  6. 1 FIELD_NAME = 'content-type'
  7. 1 CAPITALIZED_FIELD = 'Content-Type'
  8. 1 def initialize(value = nil, charset = 'utf-8')
  9. self.charset = charset
  10. if value.class == Array
  11. @main_type = value[0]
  12. @sub_type = value[1]
  13. @parameters = ParameterHash.new.merge!(value.last)
  14. else
  15. @main_type = nil
  16. @sub_type = nil
  17. @parameters = nil
  18. value = value.to_s
  19. end
  20. value = ensure_filename_quoted(value)
  21. super(CAPITALIZED_FIELD, value, charset)
  22. self.parse
  23. self
  24. end
  25. 1 def parse(val = value)
  26. unless Utilities.blank?(val)
  27. self.value = val
  28. @element = nil
  29. element
  30. end
  31. end
  32. 1 def element
  33. begin
  34. @element ||= Mail::ContentTypeElement.new(value)
  35. rescue
  36. attempt_to_clean
  37. end
  38. end
  39. 1 def attempt_to_clean
  40. # Sanitize the value, handle special cases
  41. @element ||= Mail::ContentTypeElement.new(sanatize(value))
  42. rescue
  43. # All else fails, just get the MIME media type
  44. @element ||= Mail::ContentTypeElement.new(get_mime_type(value))
  45. end
  46. 1 def main_type
  47. @main_type ||= element.main_type
  48. end
  49. 1 def sub_type
  50. @sub_type ||= element.sub_type
  51. end
  52. 1 def string
  53. "#{main_type}/#{sub_type}"
  54. end
  55. 1 def default
  56. decoded
  57. end
  58. 1 alias :content_type :string
  59. 1 def parameters
  60. unless @parameters
  61. @parameters = ParameterHash.new
  62. element.parameters.each { |p| @parameters.merge!(p) }
  63. end
  64. @parameters
  65. end
  66. 1 def ContentTypeField.with_boundary(type)
  67. new("#{type}; boundary=#{generate_boundary}")
  68. end
  69. 1 def ContentTypeField.generate_boundary
  70. "--==_mimepart_#{Mail.random_tag}"
  71. end
  72. 1 def value
  73. if @value.class == Array
  74. "#{@main_type}/#{@sub_type}; #{stringify(parameters)}"
  75. else
  76. @value
  77. end
  78. end
  79. 1 def stringify(params)
  80. params.map { |k,v| "#{k}=#{Encodings.param_encode(v)}" }.join("; ")
  81. end
  82. 1 def filename
  83. case
  84. when parameters['filename']
  85. @filename = parameters['filename']
  86. when parameters['name']
  87. @filename = parameters['name']
  88. else
  89. @filename = nil
  90. end
  91. @filename
  92. end
  93. # TODO: Fix this up
  94. 1 def encoded
  95. if parameters.length > 0
  96. p = ";\r\n\s#{parameters.encoded}"
  97. else
  98. p = ""
  99. end
  100. "#{CAPITALIZED_FIELD}: #{content_type}#{p}\r\n"
  101. end
  102. 1 def decoded
  103. if parameters.length > 0
  104. p = "; #{parameters.decoded}"
  105. else
  106. p = ""
  107. end
  108. "#{content_type}" + p
  109. end
  110. 1 private
  111. 1 def method_missing(name, *args, &block)
  112. if name.to_s =~ /(\w+)=/
  113. self.parameters[$1] = args.first
  114. @value = "#{content_type}; #{stringify(parameters)}"
  115. else
  116. super
  117. end
  118. end
  119. # Various special cases from random emails found that I am not going to change
  120. # the parser for
  121. 1 def sanatize( val )
  122. # TODO: check if there are cases where whitespace is not a separator
  123. val = val.
  124. gsub(/\s*=\s*/,'='). # remove whitespaces around equal sign
  125. gsub(/[; ]+/, '; '). #use '; ' as a separator (or EOL)
  126. gsub(/;\s*$/,'') #remove trailing to keep examples below
  127. if val =~ /(boundary=(\S*))/i
  128. val = "#{$`.downcase}boundary=#{$2}#{$'.downcase}"
  129. else
  130. val.downcase!
  131. end
  132. case
  133. when val.chomp =~ /^\s*([\w\-]+)\/([\w\-]+)\s*;\s?(ISO[\w\-]+)$/i
  134. # Microsoft helper:
  135. # Handles 'type/subtype;ISO-8559-1'
  136. "#{$1}/#{$2}; charset=#{quote_atom($3)}"
  137. when val.chomp =~ /^text;?$/i
  138. # Handles 'text;' and 'text'
  139. "text/plain;"
  140. when val.chomp =~ /^(\w+);\s(.*)$/i
  141. # Handles 'text; <parameters>'
  142. "text/plain; #{$2}"
  143. when val =~ /([\w\-]+\/[\w\-]+);\scharset="charset="(\w+)""/i
  144. # Handles text/html; charset="charset="GB2312""
  145. "#{$1}; charset=#{quote_atom($2)}"
  146. when val =~ /([\w\-]+\/[\w\-]+);\s+(.*)/i
  147. type = $1
  148. # Handles misquoted param values
  149. # e.g: application/octet-stream; name=archiveshelp1[1].htm
  150. # and: audio/x-midi;\r\n\sname=Part .exe
  151. params = $2.to_s.split(/\s+/)
  152. params = params.map { |i| i.to_s.chomp.strip }
  153. params = params.map { |i| i.split(/\s*\=\s*/, 2) }
  154. params = params.map { |i| "#{i[0]}=#{dquote(i[1].to_s.gsub(/;$/,""))}" }.join('; ')
  155. "#{type}; #{params}"
  156. when val =~ /^\s*$/
  157. 'text/plain'
  158. else
  159. val
  160. end
  161. end
  162. 1 def get_mime_type( val )
  163. case
  164. when val =~ /^([\w\-]+)\/([\w\-]+);.+$/i
  165. "#{$1}/#{$2}"
  166. else
  167. 'text/plain'
  168. end
  169. end
  170. end
  171. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/date_field.rb

50.0% lines covered

18 relevant lines. 9 lines covered and 9 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. #
  4. # = Date Field
  5. #
  6. # The Date field inherits from StructuredField and handles the Date: header
  7. # field in the email.
  8. #
  9. # Sending date to a mail message will instantiate a Mail::Field object that
  10. # has a DateField as its field type. This includes all Mail::CommonAddress
  11. # module instance methods.
  12. #
  13. # There must be excatly one Date field in an RFC2822 email.
  14. #
  15. # == Examples:
  16. #
  17. # mail = Mail.new
  18. # mail.date = 'Mon, 24 Nov 1997 14:22:01 -0800'
  19. # mail.date #=> #<DateTime: 211747170121/86400,-1/3,2299161>
  20. # mail.date.to_s #=> 'Mon, 24 Nov 1997 14:22:01 -0800'
  21. # mail[:date] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::DateField:0x180e1c4
  22. # mail['date'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::DateField:0x180e1c4
  23. # mail['Date'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::DateField:0x180e1c4
  24. #
  25. 1 require 'mail/fields/common/common_date'
  26. 1 module Mail
  27. 1 class DateField < StructuredField
  28. 1 include Mail::CommonDate
  29. 1 FIELD_NAME = 'date'
  30. 1 CAPITALIZED_FIELD = "Date"
  31. 1 def initialize(value = nil, charset = 'utf-8')
  32. self.charset = charset
  33. if Utilities.blank?(value)
  34. value = ::DateTime.now.strftime('%a, %d %b %Y %H:%M:%S %z')
  35. else
  36. value = value.to_s.gsub(/\(.*?\)/, '').squeeze(' ')
  37. value = ::DateTime.parse(value).strftime('%a, %d %b %Y %H:%M:%S %z')
  38. end
  39. super(CAPITALIZED_FIELD, value, charset)
  40. rescue ArgumentError => e
  41. raise e unless "invalid date"==e.message
  42. end
  43. 1 def encoded
  44. do_encode(CAPITALIZED_FIELD)
  45. end
  46. 1 def decoded
  47. do_decode
  48. end
  49. end
  50. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/from_field.rb

64.29% lines covered

14 relevant lines. 9 lines covered and 5 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. #
  4. # = From Field
  5. #
  6. # The From field inherits from StructuredField and handles the From: header
  7. # field in the email.
  8. #
  9. # Sending from to a mail message will instantiate a Mail::Field object that
  10. # has a FromField as its field type. This includes all Mail::CommonAddress
  11. # module instance metods.
  12. #
  13. # Only one From field can appear in a header, though it can have multiple
  14. # addresses and groups of addresses.
  15. #
  16. # == Examples:
  17. #
  18. # mail = Mail.new
  19. # mail.from = 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  20. # mail.from #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  21. # mail[:from] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::FromField:0x180e1c4
  22. # mail['from'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::FromField:0x180e1c4
  23. # mail['From'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::FromField:0x180e1c4
  24. #
  25. # mail[:from].encoded #=> 'from: Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net\r\n'
  26. # mail[:from].decoded #=> 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  27. # mail[:from].addresses #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  28. # mail[:from].formatted #=> ['Mikel Lindsaar <mikel@test.lindsaar.net>', 'ada@test.lindsaar.net']
  29. #
  30. 1 require 'mail/fields/common/common_address'
  31. 1 module Mail
  32. 1 class FromField < StructuredField
  33. 1 include Mail::CommonAddress
  34. 1 FIELD_NAME = 'from'
  35. 1 CAPITALIZED_FIELD = 'From'
  36. 1 def initialize(value = nil, charset = 'utf-8')
  37. self.charset = charset
  38. super(CAPITALIZED_FIELD, value, charset)
  39. self
  40. end
  41. 1 def encoded
  42. do_encode(CAPITALIZED_FIELD)
  43. end
  44. 1 def decoded
  45. do_decode
  46. end
  47. end
  48. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/in_reply_to_field.rb

56.25% lines covered

16 relevant lines. 9 lines covered and 7 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. #
  4. # = In-Reply-To Field
  5. #
  6. # The In-Reply-To field inherits from StructuredField and handles the
  7. # In-Reply-To: header field in the email.
  8. #
  9. # Sending in_reply_to to a mail message will instantiate a Mail::Field object that
  10. # has a InReplyToField as its field type. This includes all Mail::CommonMessageId
  11. # module instance metods.
  12. #
  13. # Note that, the #message_ids method will return an array of message IDs without the
  14. # enclosing angle brackets which per RFC are not syntactically part of the message id.
  15. #
  16. # Only one InReplyTo field can appear in a header, though it can have multiple
  17. # Message IDs.
  18. #
  19. # == Examples:
  20. #
  21. # mail = Mail.new
  22. # mail.in_reply_to = '<F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@test.me.dom>'
  23. # mail.in_reply_to #=> '<F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@test.me.dom>'
  24. # mail[:in_reply_to] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::InReplyToField:0x180e1c4
  25. # mail['in_reply_to'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::InReplyToField:0x180e1c4
  26. # mail['In-Reply-To'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::InReplyToField:0x180e1c4
  27. #
  28. # mail[:in_reply_to].message_ids #=> ['F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@test.me.dom']
  29. #
  30. 1 require 'mail/fields/common/common_message_id'
  31. 1 module Mail
  32. 1 class InReplyToField < StructuredField
  33. 1 include Mail::CommonMessageId
  34. 1 FIELD_NAME = 'in-reply-to'
  35. 1 CAPITALIZED_FIELD = 'In-Reply-To'
  36. 1 def initialize(value = nil, charset = 'utf-8')
  37. self.charset = charset
  38. value = value.join("\r\n\s") if value.is_a?(Array)
  39. super(CAPITALIZED_FIELD, value, charset)
  40. self.parse
  41. self
  42. end
  43. 1 def encoded
  44. do_encode(CAPITALIZED_FIELD)
  45. end
  46. 1 def decoded
  47. do_decode
  48. end
  49. end
  50. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/keywords_field.rb

52.38% lines covered

21 relevant lines. 11 lines covered and 10 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. #
  4. # keywords = "Keywords:" phrase *("," phrase) CRLF
  5. 1 module Mail
  6. 1 class KeywordsField < StructuredField
  7. 1 FIELD_NAME = 'keywords'
  8. 1 CAPITALIZED_FIELD = 'Keywords'
  9. 1 def initialize(value = nil, charset = 'utf-8')
  10. self.charset = charset
  11. super(CAPITALIZED_FIELD, value, charset)
  12. self
  13. end
  14. 1 def parse(val = value)
  15. unless Utilities.blank?(val)
  16. @phrase_list ||= PhraseList.new(value)
  17. end
  18. end
  19. 1 def phrase_list
  20. @phrase_list ||= PhraseList.new(value)
  21. end
  22. 1 def keywords
  23. phrase_list.phrases
  24. end
  25. 1 def encoded
  26. "#{CAPITALIZED_FIELD}: #{keywords.join(",\r\n ")}\r\n"
  27. end
  28. 1 def decoded
  29. keywords.join(', ')
  30. end
  31. 1 def default
  32. keywords
  33. end
  34. end
  35. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/message_id_field.rb

50.0% lines covered

28 relevant lines. 14 lines covered and 14 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. #
  4. # = Message-ID Field
  5. #
  6. # The Message-ID field inherits from StructuredField and handles the
  7. # Message-ID: header field in the email.
  8. #
  9. # Sending message_id to a mail message will instantiate a Mail::Field object that
  10. # has a MessageIdField as its field type. This includes all Mail::CommonMessageId
  11. # module instance metods.
  12. #
  13. # Only one MessageId field can appear in a header, and syntactically it can only have
  14. # one Message ID. The message_ids method call has been left in however as it will only
  15. # return the one message id, ie, an array of length 1.
  16. #
  17. # Note that, the #message_ids method will return an array of message IDs without the
  18. # enclosing angle brackets which per RFC are not syntactically part of the message id.
  19. #
  20. # == Examples:
  21. #
  22. # mail = Mail.new
  23. # mail.message_id = '<F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@test.me.dom>'
  24. # mail.message_id #=> '<F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@test.me.dom>'
  25. # mail[:message_id] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::MessageIdField:0x180e1c4
  26. # mail['message_id'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::MessageIdField:0x180e1c4
  27. # mail['Message-ID'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::MessageIdField:0x180e1c4
  28. #
  29. # mail[:message_id].message_id #=> 'F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@test.me.dom'
  30. # mail[:message_id].message_ids #=> ['F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@test.me.dom']
  31. #
  32. 1 require 'mail/fields/common/common_message_id'
  33. 1 module Mail
  34. 1 class MessageIdField < StructuredField
  35. 1 include Mail::CommonMessageId
  36. 1 FIELD_NAME = 'message-id'
  37. 1 CAPITALIZED_FIELD = 'Message-ID'
  38. 1 def initialize(value = nil, charset = 'utf-8')
  39. self.charset = charset
  40. @uniq = 1
  41. if Utilities.blank?(value)
  42. self.name = CAPITALIZED_FIELD
  43. self.value = generate_message_id
  44. else
  45. super(CAPITALIZED_FIELD, value, charset)
  46. end
  47. self.parse
  48. self
  49. end
  50. 1 def name
  51. 'Message-ID'
  52. end
  53. 1 def message_ids
  54. [message_id]
  55. end
  56. 1 def to_s
  57. "<#{message_id}>"
  58. end
  59. 1 def encoded
  60. do_encode(CAPITALIZED_FIELD)
  61. end
  62. 1 def decoded
  63. do_decode
  64. end
  65. 1 private
  66. 1 def generate_message_id
  67. "<#{Mail.random_tag}@#{::Socket.gethostname}.mail>"
  68. end
  69. end
  70. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/mime_version_field.rb

46.15% lines covered

26 relevant lines. 12 lines covered and 14 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. #
  4. #
  5. #
  6. 1 module Mail
  7. 1 class MimeVersionField < StructuredField
  8. 1 FIELD_NAME = 'mime-version'
  9. 1 CAPITALIZED_FIELD = 'Mime-Version'
  10. 1 def initialize(value = nil, charset = 'utf-8')
  11. self.charset = charset
  12. if Utilities.blank?(value)
  13. value = '1.0'
  14. end
  15. super(CAPITALIZED_FIELD, value, charset)
  16. self.parse
  17. self
  18. end
  19. 1 def parse(val = value)
  20. unless Utilities.blank?(val)
  21. @element = Mail::MimeVersionElement.new(val)
  22. end
  23. end
  24. 1 def element
  25. @element ||= Mail::MimeVersionElement.new(value)
  26. end
  27. 1 def version
  28. "#{element.major}.#{element.minor}"
  29. end
  30. 1 def major
  31. element.major.to_i
  32. end
  33. 1 def minor
  34. element.minor.to_i
  35. end
  36. 1 def encoded
  37. "#{CAPITALIZED_FIELD}: #{version}\r\n"
  38. end
  39. 1 def decoded
  40. version
  41. end
  42. end
  43. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/received_field.rb

42.86% lines covered

28 relevant lines. 12 lines covered and 16 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. #
  4. # trace = [return]
  5. # 1*received
  6. #
  7. # return = "Return-Path:" path CRLF
  8. #
  9. # path = ([CFWS] "<" ([CFWS] / addr-spec) ">" [CFWS]) /
  10. # obs-path
  11. #
  12. # received = "Received:" name-val-list ";" date-time CRLF
  13. #
  14. # name-val-list = [CFWS] [name-val-pair *(CFWS name-val-pair)]
  15. #
  16. # name-val-pair = item-name CFWS item-value
  17. #
  18. # item-name = ALPHA *(["-"] (ALPHA / DIGIT))
  19. #
  20. # item-value = 1*angle-addr / addr-spec /
  21. # atom / domain / msg-id
  22. #
  23. 1 module Mail
  24. 1 class ReceivedField < StructuredField
  25. 1 FIELD_NAME = 'received'
  26. 1 CAPITALIZED_FIELD = 'Received'
  27. 1 def initialize(value = nil, charset = 'utf-8')
  28. self.charset = charset
  29. super(CAPITALIZED_FIELD, value, charset)
  30. self.parse
  31. self
  32. end
  33. 1 def parse(val = value)
  34. unless Utilities.blank?(val)
  35. @element = Mail::ReceivedElement.new(val)
  36. end
  37. end
  38. 1 def element
  39. @element ||= Mail::ReceivedElement.new(value)
  40. end
  41. 1 def date_time
  42. @datetime ||= ::DateTime.parse("#{element.date_time}")
  43. end
  44. 1 def info
  45. element.info
  46. end
  47. 1 def formatted_date
  48. date_time.strftime("%a, %d %b %Y %H:%M:%S ") + date_time.zone.delete(':')
  49. end
  50. 1 def encoded
  51. if Utilities.blank?(value)
  52. "#{CAPITALIZED_FIELD}: \r\n"
  53. else
  54. "#{CAPITALIZED_FIELD}: #{info}; #{formatted_date}\r\n"
  55. end
  56. end
  57. 1 def decoded
  58. if Utilities.blank?(value)
  59. ""
  60. else
  61. "#{info}; #{formatted_date}"
  62. end
  63. end
  64. end
  65. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/references_field.rb

56.25% lines covered

16 relevant lines. 9 lines covered and 7 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. #
  4. # = References Field
  5. #
  6. # The References field inherits references StructuredField and handles the References: header
  7. # field in the email.
  8. #
  9. # Sending references to a mail message will instantiate a Mail::Field object that
  10. # has a ReferencesField as its field type. This includes all Mail::CommonAddress
  11. # module instance metods.
  12. #
  13. # Note that, the #message_ids method will return an array of message IDs without the
  14. # enclosing angle brackets which per RFC are not syntactically part of the message id.
  15. #
  16. # Only one References field can appear in a header, though it can have multiple
  17. # Message IDs.
  18. #
  19. # == Examples:
  20. #
  21. # mail = Mail.new
  22. # mail.references = '<F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@test.me.dom>'
  23. # mail.references #=> '<F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@test.me.dom>'
  24. # mail[:references] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ReferencesField:0x180e1c4
  25. # mail['references'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ReferencesField:0x180e1c4
  26. # mail['References'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ReferencesField:0x180e1c4
  27. #
  28. # mail[:references].message_ids #=> ['F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@test.me.dom']
  29. #
  30. 1 require 'mail/fields/common/common_message_id'
  31. 1 module Mail
  32. 1 class ReferencesField < StructuredField
  33. 1 include CommonMessageId
  34. 1 FIELD_NAME = 'references'
  35. 1 CAPITALIZED_FIELD = 'References'
  36. 1 def initialize(value = nil, charset = 'utf-8')
  37. self.charset = charset
  38. value = value.join("\r\n\s") if value.is_a?(Array)
  39. super(CAPITALIZED_FIELD, value, charset)
  40. self.parse
  41. self
  42. end
  43. 1 def encoded
  44. do_encode(CAPITALIZED_FIELD)
  45. end
  46. 1 def decoded
  47. do_decode
  48. end
  49. end
  50. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/reply_to_field.rb

64.29% lines covered

14 relevant lines. 9 lines covered and 5 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. #
  4. # = Reply-To Field
  5. #
  6. # The Reply-To field inherits reply-to StructuredField and handles the Reply-To: header
  7. # field in the email.
  8. #
  9. # Sending reply_to to a mail message will instantiate a Mail::Field object that
  10. # has a ReplyToField as its field type. This includes all Mail::CommonAddress
  11. # module instance metods.
  12. #
  13. # Only one Reply-To field can appear in a header, though it can have multiple
  14. # addresses and groups of addresses.
  15. #
  16. # == Examples:
  17. #
  18. # mail = Mail.new
  19. # mail.reply_to = 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  20. # mail.reply_to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  21. # mail[:reply_to] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ReplyToField:0x180e1c4
  22. # mail['reply-to'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ReplyToField:0x180e1c4
  23. # mail['Reply-To'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ReplyToField:0x180e1c4
  24. #
  25. # mail[:reply_to].encoded #=> 'Reply-To: Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net\r\n'
  26. # mail[:reply_to].decoded #=> 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  27. # mail[:reply_to].addresses #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  28. # mail[:reply_to].formatted #=> ['Mikel Lindsaar <mikel@test.lindsaar.net>', 'ada@test.lindsaar.net']
  29. #
  30. 1 require 'mail/fields/common/common_address'
  31. 1 module Mail
  32. 1 class ReplyToField < StructuredField
  33. 1 include Mail::CommonAddress
  34. 1 FIELD_NAME = 'reply-to'
  35. 1 CAPITALIZED_FIELD = 'Reply-To'
  36. 1 def initialize(value = nil, charset = 'utf-8')
  37. self.charset = charset
  38. super(CAPITALIZED_FIELD, value, charset)
  39. self
  40. end
  41. 1 def encoded
  42. do_encode(CAPITALIZED_FIELD)
  43. end
  44. 1 def decoded
  45. do_decode
  46. end
  47. end
  48. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/resent_bcc_field.rb

64.29% lines covered

14 relevant lines. 9 lines covered and 5 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. #
  4. # = Resent-Bcc Field
  5. #
  6. # The Resent-Bcc field inherits resent-bcc StructuredField and handles the
  7. # Resent-Bcc: header field in the email.
  8. #
  9. # Sending resent_bcc to a mail message will instantiate a Mail::Field object that
  10. # has a ResentBccField as its field type. This includes all Mail::CommonAddress
  11. # module instance metods.
  12. #
  13. # Only one Resent-Bcc field can appear in a header, though it can have multiple
  14. # addresses and groups of addresses.
  15. #
  16. # == Examples:
  17. #
  18. # mail = Mail.new
  19. # mail.resent_bcc = 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  20. # mail.resent_bcc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  21. # mail[:resent_bcc] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ResentBccField:0x180e1c4
  22. # mail['resent-bcc'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ResentBccField:0x180e1c4
  23. # mail['Resent-Bcc'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ResentBccField:0x180e1c4
  24. #
  25. # mail[:resent_bcc].encoded #=> 'Resent-Bcc: Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net\r\n'
  26. # mail[:resent_bcc].decoded #=> 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  27. # mail[:resent_bcc].addresses #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  28. # mail[:resent_bcc].formatted #=> ['Mikel Lindsaar <mikel@test.lindsaar.net>', 'ada@test.lindsaar.net']
  29. #
  30. 1 require 'mail/fields/common/common_address'
  31. 1 module Mail
  32. 1 class ResentBccField < StructuredField
  33. 1 include Mail::CommonAddress
  34. 1 FIELD_NAME = 'resent-bcc'
  35. 1 CAPITALIZED_FIELD = 'Resent-Bcc'
  36. 1 def initialize(value = nil, charset = 'utf-8')
  37. self.charset = charset
  38. super(CAPITALIZED_FIELD, value, charset)
  39. self
  40. end
  41. 1 def encoded
  42. do_encode(CAPITALIZED_FIELD)
  43. end
  44. 1 def decoded
  45. do_decode
  46. end
  47. end
  48. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/resent_cc_field.rb

64.29% lines covered

14 relevant lines. 9 lines covered and 5 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. #
  4. # = Resent-Cc Field
  5. #
  6. # The Resent-Cc field inherits resent-cc StructuredField and handles the Resent-Cc: header
  7. # field in the email.
  8. #
  9. # Sending resent_cc to a mail message will instantiate a Mail::Field object that
  10. # has a ResentCcField as its field type. This includes all Mail::CommonAddress
  11. # module instance metods.
  12. #
  13. # Only one Resent-Cc field can appear in a header, though it can have multiple
  14. # addresses and groups of addresses.
  15. #
  16. # == Examples:
  17. #
  18. # mail = Mail.new
  19. # mail.resent_cc = 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  20. # mail.resent_cc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  21. # mail[:resent_cc] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ResentCcField:0x180e1c4
  22. # mail['resent-cc'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ResentCcField:0x180e1c4
  23. # mail['Resent-Cc'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ResentCcField:0x180e1c4
  24. #
  25. # mail[:resent_cc].encoded #=> 'Resent-Cc: Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net\r\n'
  26. # mail[:resent_cc].decoded #=> 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  27. # mail[:resent_cc].addresses #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  28. # mail[:resent_cc].formatted #=> ['Mikel Lindsaar <mikel@test.lindsaar.net>', 'ada@test.lindsaar.net']
  29. #
  30. 1 require 'mail/fields/common/common_address'
  31. 1 module Mail
  32. 1 class ResentCcField < StructuredField
  33. 1 include Mail::CommonAddress
  34. 1 FIELD_NAME = 'resent-cc'
  35. 1 CAPITALIZED_FIELD = 'Resent-Cc'
  36. 1 def initialize(value = nil, charset = 'utf-8')
  37. self.charset = charset
  38. super(CAPITALIZED_FIELD, value, charset)
  39. self
  40. end
  41. 1 def encoded
  42. do_encode(CAPITALIZED_FIELD)
  43. end
  44. 1 def decoded
  45. do_decode
  46. end
  47. end
  48. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/resent_date_field.rb

52.94% lines covered

17 relevant lines. 9 lines covered and 8 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. #
  4. # resent-date = "Resent-Date:" date-time CRLF
  5. 1 require 'mail/fields/common/common_date'
  6. 1 module Mail
  7. 1 class ResentDateField < StructuredField
  8. 1 include Mail::CommonDate
  9. 1 FIELD_NAME = 'resent-date'
  10. 1 CAPITALIZED_FIELD = 'Resent-Date'
  11. 1 def initialize(value = nil, charset = 'utf-8')
  12. self.charset = charset
  13. if Utilities.blank?(value)
  14. value = ::DateTime.now.strftime('%a, %d %b %Y %H:%M:%S %z')
  15. else
  16. value = ::DateTime.parse(value.to_s).strftime('%a, %d %b %Y %H:%M:%S %z')
  17. end
  18. super(CAPITALIZED_FIELD, value, charset)
  19. self
  20. end
  21. 1 def encoded
  22. do_encode(CAPITALIZED_FIELD)
  23. end
  24. 1 def decoded
  25. do_decode
  26. end
  27. end
  28. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/resent_from_field.rb

64.29% lines covered

14 relevant lines. 9 lines covered and 5 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. #
  4. # = Resent-From Field
  5. #
  6. # The Resent-From field inherits resent-from StructuredField and handles the Resent-From: header
  7. # field in the email.
  8. #
  9. # Sending resent_from to a mail message will instantiate a Mail::Field object that
  10. # has a ResentFromField as its field type. This includes all Mail::CommonAddress
  11. # module instance metods.
  12. #
  13. # Only one Resent-From field can appear in a header, though it can have multiple
  14. # addresses and groups of addresses.
  15. #
  16. # == Examples:
  17. #
  18. # mail = Mail.new
  19. # mail.resent_from = 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  20. # mail.resent_from #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  21. # mail[:resent_from] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ResentFromField:0x180e1c4
  22. # mail['resent-from'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ResentFromField:0x180e1c4
  23. # mail['Resent-From'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ResentFromField:0x180e1c4
  24. #
  25. # mail[:resent_from].encoded #=> 'Resent-From: Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net\r\n'
  26. # mail[:resent_from].decoded #=> 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  27. # mail[:resent_from].addresses #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  28. # mail[:resent_from].formatted #=> ['Mikel Lindsaar <mikel@test.lindsaar.net>', 'ada@test.lindsaar.net']
  29. #
  30. 1 require 'mail/fields/common/common_address'
  31. 1 module Mail
  32. 1 class ResentFromField < StructuredField
  33. 1 include Mail::CommonAddress
  34. 1 FIELD_NAME = 'resent-from'
  35. 1 CAPITALIZED_FIELD = 'Resent-From'
  36. 1 def initialize(value = nil, charset = 'utf-8')
  37. self.charset = charset
  38. super(CAPITALIZED_FIELD, value, charset)
  39. self
  40. end
  41. 1 def encoded
  42. do_encode(CAPITALIZED_FIELD)
  43. end
  44. 1 def decoded
  45. do_decode
  46. end
  47. end
  48. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/resent_message_id_field.rb

58.82% lines covered

17 relevant lines. 10 lines covered and 7 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. #
  4. # resent-msg-id = "Resent-Message-ID:" msg-id CRLF
  5. 1 require 'mail/fields/common/common_message_id'
  6. 1 module Mail
  7. 1 class ResentMessageIdField < StructuredField
  8. 1 include CommonMessageId
  9. 1 FIELD_NAME = 'resent-message-id'
  10. 1 CAPITALIZED_FIELD = 'Resent-Message-ID'
  11. 1 def initialize(value = nil, charset = 'utf-8')
  12. self.charset = charset
  13. super(CAPITALIZED_FIELD, value, charset)
  14. self.parse
  15. self
  16. end
  17. 1 def name
  18. 'Resent-Message-ID'
  19. end
  20. 1 def encoded
  21. do_encode(CAPITALIZED_FIELD)
  22. end
  23. 1 def decoded
  24. do_decode
  25. end
  26. end
  27. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/resent_sender_field.rb

61.11% lines covered

18 relevant lines. 11 lines covered and 7 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. #
  4. # = Resent-Sender Field
  5. #
  6. # The Resent-Sender field inherits resent-sender StructuredField and handles the Resent-Sender: header
  7. # field in the email.
  8. #
  9. # Sending resent_sender to a mail message will instantiate a Mail::Field object that
  10. # has a ResentSenderField as its field type. This includes all Mail::CommonAddress
  11. # module instance metods.
  12. #
  13. # Only one Resent-Sender field can appear in a header, though it can have multiple
  14. # addresses and groups of addresses.
  15. #
  16. # == Examples:
  17. #
  18. # mail = Mail.new
  19. # mail.resent_sender = 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  20. # mail.resent_sender #=> ['mikel@test.lindsaar.net']
  21. # mail[:resent_sender] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ResentSenderField:0x180e1c4
  22. # mail['resent-sender'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ResentSenderField:0x180e1c4
  23. # mail['Resent-Sender'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ResentSenderField:0x180e1c4
  24. #
  25. # mail.resent_sender.to_s #=> 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  26. # mail.resent_sender.addresses #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  27. # mail.resent_sender.formatted #=> ['Mikel Lindsaar <mikel@test.lindsaar.net>', 'ada@test.lindsaar.net']
  28. #
  29. 1 require 'mail/fields/common/common_address'
  30. 1 module Mail
  31. 1 class ResentSenderField < StructuredField
  32. 1 include Mail::CommonAddress
  33. 1 FIELD_NAME = 'resent-sender'
  34. 1 CAPITALIZED_FIELD = 'Resent-Sender'
  35. 1 def initialize(value = nil, charset = 'utf-8')
  36. self.charset = charset
  37. super(CAPITALIZED_FIELD, value, charset)
  38. self
  39. end
  40. 1 def addresses
  41. [address.address]
  42. end
  43. 1 def address
  44. address_list.addresses.first
  45. end
  46. 1 def encoded
  47. do_encode(CAPITALIZED_FIELD)
  48. end
  49. 1 def decoded
  50. do_decode
  51. end
  52. end
  53. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/resent_to_field.rb

64.29% lines covered

14 relevant lines. 9 lines covered and 5 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. #
  4. # = Resent-To Field
  5. #
  6. # The Resent-To field inherits resent-to StructuredField and handles the Resent-To: header
  7. # field in the email.
  8. #
  9. # Sending resent_to to a mail message will instantiate a Mail::Field object that
  10. # has a ResentToField as its field type. This includes all Mail::CommonAddress
  11. # module instance metods.
  12. #
  13. # Only one Resent-To field can appear in a header, though it can have multiple
  14. # addresses and groups of addresses.
  15. #
  16. # == Examples:
  17. #
  18. # mail = Mail.new
  19. # mail.resent_to = 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  20. # mail.resent_to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  21. # mail[:resent_to] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ResentToField:0x180e1c4
  22. # mail['resent-to'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ResentToField:0x180e1c4
  23. # mail['Resent-To'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ResentToField:0x180e1c4
  24. #
  25. # mail[:resent_to].encoded #=> 'Resent-To: Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net\r\n'
  26. # mail[:resent_to].decoded #=> 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  27. # mail[:resent_to].addresses #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  28. # mail[:resent_to].formatted #=> ['Mikel Lindsaar <mikel@test.lindsaar.net>', 'ada@test.lindsaar.net']
  29. #
  30. 1 require 'mail/fields/common/common_address'
  31. 1 module Mail
  32. 1 class ResentToField < StructuredField
  33. 1 include Mail::CommonAddress
  34. 1 FIELD_NAME = 'resent-to'
  35. 1 CAPITALIZED_FIELD = 'Resent-To'
  36. 1 def initialize(value = nil, charset = 'utf-8')
  37. self.charset = charset
  38. super(CAPITALIZED_FIELD, value, charset)
  39. self
  40. end
  41. 1 def encoded
  42. do_encode(CAPITALIZED_FIELD)
  43. end
  44. 1 def decoded
  45. do_decode
  46. end
  47. end
  48. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/return_path_field.rb

57.89% lines covered

19 relevant lines. 11 lines covered and 8 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. #
  4. # 4.4.3. REPLY-TO / RESENT-REPLY-TO
  5. #
  6. # Note: The "Return-Path" field is added by the mail transport
  7. # service, at the time of final deliver. It is intended
  8. # to identify a path back to the orginator of the mes-
  9. # sage. The "Reply-To" field is added by the message
  10. # originator and is intended to direct replies.
  11. #
  12. # trace = [return]
  13. # 1*received
  14. #
  15. # return = "Return-Path:" path CRLF
  16. #
  17. # path = ([CFWS] "<" ([CFWS] / addr-spec) ">" [CFWS]) /
  18. # obs-path
  19. #
  20. # received = "Received:" name-val-list ";" date-time CRLF
  21. #
  22. # name-val-list = [CFWS] [name-val-pair *(CFWS name-val-pair)]
  23. #
  24. # name-val-pair = item-name CFWS item-value
  25. #
  26. # item-name = ALPHA *(["-"] (ALPHA / DIGIT))
  27. #
  28. # item-value = 1*angle-addr / addr-spec /
  29. # atom / domain / msg-id
  30. #
  31. 1 require 'mail/fields/common/common_address'
  32. 1 module Mail
  33. 1 class ReturnPathField < StructuredField
  34. 1 include Mail::CommonAddress
  35. 1 FIELD_NAME = 'return-path'
  36. 1 CAPITALIZED_FIELD = 'Return-Path'
  37. 1 def initialize(value = nil, charset = 'utf-8')
  38. value = nil if value == '<>'
  39. self.charset = charset
  40. super(CAPITALIZED_FIELD, value, charset)
  41. self
  42. end
  43. 1 def encoded
  44. "#{CAPITALIZED_FIELD}: <#{address}>\r\n"
  45. end
  46. 1 def decoded
  47. do_decode
  48. end
  49. 1 def address
  50. addresses.first
  51. end
  52. 1 def default
  53. address
  54. end
  55. end
  56. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/sender_field.rb

60.0% lines covered

20 relevant lines. 12 lines covered and 8 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. #
  4. # = Sender Field
  5. #
  6. # The Sender field inherits sender StructuredField and handles the Sender: header
  7. # field in the email.
  8. #
  9. # Sending sender to a mail message will instantiate a Mail::Field object that
  10. # has a SenderField as its field type. This includes all Mail::CommonAddress
  11. # module instance metods.
  12. #
  13. # Only one Sender field can appear in a header, though it can have multiple
  14. # addresses and groups of addresses.
  15. #
  16. # == Examples:
  17. #
  18. # mail = Mail.new
  19. # mail.sender = 'Mikel Lindsaar <mikel@test.lindsaar.net>'
  20. # mail.sender #=> 'mikel@test.lindsaar.net'
  21. # mail[:sender] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::SenderField:0x180e1c4
  22. # mail['sender'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::SenderField:0x180e1c4
  23. # mail['Sender'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::SenderField:0x180e1c4
  24. #
  25. # mail[:sender].encoded #=> "Sender: Mikel Lindsaar <mikel@test.lindsaar.net>\r\n"
  26. # mail[:sender].decoded #=> 'Mikel Lindsaar <mikel@test.lindsaar.net>'
  27. # mail[:sender].addresses #=> ['mikel@test.lindsaar.net']
  28. # mail[:sender].formatted #=> ['Mikel Lindsaar <mikel@test.lindsaar.net>']
  29. #
  30. 1 require 'mail/fields/common/common_address'
  31. 1 module Mail
  32. 1 class SenderField < StructuredField
  33. 1 include Mail::CommonAddress
  34. 1 FIELD_NAME = 'sender'
  35. 1 CAPITALIZED_FIELD = 'Sender'
  36. 1 def initialize(value = nil, charset = 'utf-8')
  37. self.charset = charset
  38. super(CAPITALIZED_FIELD, value, charset)
  39. self
  40. end
  41. 1 def addresses
  42. [address.address]
  43. end
  44. 1 def address
  45. address_list.addresses.first
  46. end
  47. 1 def encoded
  48. do_encode(CAPITALIZED_FIELD)
  49. end
  50. 1 def decoded
  51. do_decode
  52. end
  53. 1 def default
  54. address.address
  55. end
  56. end
  57. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/structured_field.rb

55.56% lines covered

18 relevant lines. 10 lines covered and 8 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. 1 require 'mail/fields/common/common_field'
  4. 1 module Mail
  5. # Provides access to a structured header field
  6. #
  7. # ===Per RFC 2822:
  8. # 2.2.2. Structured Header Field Bodies
  9. #
  10. # Some field bodies in this standard have specific syntactical
  11. # structure more restrictive than the unstructured field bodies
  12. # described above. These are referred to as "structured" field bodies.
  13. # Structured field bodies are sequences of specific lexical tokens as
  14. # described in sections 3 and 4 of this standard. Many of these tokens
  15. # are allowed (according to their syntax) to be introduced or end with
  16. # comments (as described in section 3.2.3) as well as the space (SP,
  17. # ASCII value 32) and horizontal tab (HTAB, ASCII value 9) characters
  18. # (together known as the white space characters, WSP), and those WSP
  19. # characters are subject to header "folding" and "unfolding" as
  20. # described in section 2.2.3. Semantic analysis of structured field
  21. # bodies is given along with their syntax.
  22. 1 class StructuredField
  23. 1 include Mail::CommonField
  24. 1 include Mail::Utilities
  25. 1 def initialize(name = nil, value = nil, charset = nil)
  26. self.name = name
  27. self.value = value
  28. self.charset = charset
  29. self
  30. end
  31. 1 def charset
  32. @charset
  33. end
  34. 1 def charset=(val)
  35. @charset = val
  36. end
  37. 1 def default
  38. decoded
  39. end
  40. 1 def errors
  41. []
  42. end
  43. end
  44. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/subject_field.rb

71.43% lines covered

7 relevant lines. 5 lines covered and 2 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. #
  4. # subject = "Subject:" unstructured CRLF
  5. 1 module Mail
  6. 1 class SubjectField < UnstructuredField
  7. 1 FIELD_NAME = 'subject'
  8. 1 CAPITALIZED_FIELD = "Subject"
  9. 1 def initialize(value = nil, charset = 'utf-8')
  10. self.charset = charset
  11. super(CAPITALIZED_FIELD, value, charset)
  12. end
  13. end
  14. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/to_field.rb

64.29% lines covered

14 relevant lines. 9 lines covered and 5 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. #
  4. # = To Field
  5. #
  6. # The To field inherits to StructuredField and handles the To: header
  7. # field in the email.
  8. #
  9. # Sending to to a mail message will instantiate a Mail::Field object that
  10. # has a ToField as its field type. This includes all Mail::CommonAddress
  11. # module instance metods.
  12. #
  13. # Only one To field can appear in a header, though it can have multiple
  14. # addresses and groups of addresses.
  15. #
  16. # == Examples:
  17. #
  18. # mail = Mail.new
  19. # mail.to = 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  20. # mail.to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  21. # mail[:to] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ToField:0x180e1c4
  22. # mail['to'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ToField:0x180e1c4
  23. # mail['To'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ToField:0x180e1c4
  24. #
  25. # mail[:to].encoded #=> 'To: Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net\r\n'
  26. # mail[:to].decoded #=> 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  27. # mail[:to].addresses #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  28. # mail[:to].formatted #=> ['Mikel Lindsaar <mikel@test.lindsaar.net>', 'ada@test.lindsaar.net']
  29. #
  30. 1 require 'mail/fields/common/common_address'
  31. 1 module Mail
  32. 1 class ToField < StructuredField
  33. 1 include Mail::CommonAddress
  34. 1 FIELD_NAME = 'to'
  35. 1 CAPITALIZED_FIELD = 'To'
  36. 1 def initialize(value = nil, charset = 'utf-8')
  37. self.charset = charset
  38. super(CAPITALIZED_FIELD, value, charset)
  39. self
  40. end
  41. 1 def encoded
  42. do_encode(CAPITALIZED_FIELD)
  43. end
  44. 1 def decoded
  45. do_decode
  46. end
  47. end
  48. end

target/rubygems/gems/mail-2.7.1/lib/mail/fields/unstructured_field.rb

21.65% lines covered

97 relevant lines. 21 lines covered and 76 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. 1 require 'mail/fields/common/common_field'
  4. 1 module Mail
  5. # Provides access to an unstructured header field
  6. #
  7. # ===Per RFC 2822:
  8. # 2.2.1. Unstructured Header Field Bodies
  9. #
  10. # Some field bodies in this standard are defined simply as
  11. # "unstructured" (which is specified below as any US-ASCII characters,
  12. # except for CR and LF) with no further restrictions. These are
  13. # referred to as unstructured field bodies. Semantically, unstructured
  14. # field bodies are simply to be treated as a single line of characters
  15. # with no further processing (except for header "folding" and
  16. # "unfolding" as described in section 2.2.3).
  17. 1 class UnstructuredField
  18. 1 include Mail::CommonField
  19. 1 include Mail::Utilities
  20. 1 attr_accessor :charset
  21. 1 attr_reader :errors
  22. 1 def initialize(name, value, charset = nil)
  23. @errors = []
  24. if value.is_a?(Array)
  25. # Probably has arrived here from a failed parse of an AddressList Field
  26. value = value.join(', ')
  27. else
  28. # Ensure we are dealing with a string
  29. value = value.to_s
  30. # Mark UTF-8 strings parsed from ASCII-8BIT
  31. if value.respond_to?(:force_encoding) && value.encoding == Encoding::ASCII_8BIT
  32. utf8 = value.dup.force_encoding(Encoding::UTF_8)
  33. value = utf8 if utf8.valid_encoding?
  34. end
  35. end
  36. if charset
  37. self.charset = charset
  38. else
  39. if value.respond_to?(:encoding)
  40. self.charset = value.encoding
  41. else
  42. self.charset = $KCODE
  43. end
  44. end
  45. self.name = name
  46. self.value = value
  47. self
  48. end
  49. 1 def encoded
  50. do_encode
  51. end
  52. 1 def decoded
  53. do_decode
  54. end
  55. 1 def default
  56. decoded
  57. end
  58. 1 def parse # An unstructured field does not parse
  59. self
  60. end
  61. 1 private
  62. 1 def do_encode
  63. if value && !value.empty?
  64. "#{wrapped_value}\r\n"
  65. else
  66. ''
  67. end
  68. end
  69. 1 def do_decode
  70. Utilities.blank?(value) ? nil : Encodings.decode_encode(value, :decode)
  71. end
  72. # 2.2.3. Long Header Fields
  73. #
  74. # Each header field is logically a single line of characters comprising
  75. # the field name, the colon, and the field body. For convenience
  76. # however, and to deal with the 998/78 character limitations per line,
  77. # the field body portion of a header field can be split into a multiple
  78. # line representation; this is called "folding". The general rule is
  79. # that wherever this standard allows for folding white space (not
  80. # simply WSP characters), a CRLF may be inserted before any WSP. For
  81. # example, the header field:
  82. #
  83. # Subject: This is a test
  84. #
  85. # can be represented as:
  86. #
  87. # Subject: This
  88. # is a test
  89. #
  90. # Note: Though structured field bodies are defined in such a way that
  91. # folding can take place between many of the lexical tokens (and even
  92. # within some of the lexical tokens), folding SHOULD be limited to
  93. # placing the CRLF at higher-level syntactic breaks. For instance, if
  94. # a field body is defined as comma-separated values, it is recommended
  95. # that folding occur after the comma separating the structured items in
  96. # preference to other places where the field could be folded, even if
  97. # it is allowed elsewhere.
  98. 1 def wrapped_value # :nodoc:
  99. wrap_lines(name, fold("#{name}: ".length))
  100. end
  101. # 6.2. Display of 'encoded-word's
  102. #
  103. # When displaying a particular header field that contains multiple
  104. # 'encoded-word's, any 'linear-white-space' that separates a pair of
  105. # adjacent 'encoded-word's is ignored. (This is to allow the use of
  106. # multiple 'encoded-word's to represent long strings of unencoded text,
  107. # without having to separate 'encoded-word's where spaces occur in the
  108. # unencoded text.)
  109. 1 def wrap_lines(name, folded_lines)
  110. result = ["#{name}: #{folded_lines.shift}"]
  111. result.concat(folded_lines)
  112. result.join("\r\n\s")
  113. end
  114. 1 def fold(prepend = 0) # :nodoc:
  115. encoding = normalized_encoding
  116. decoded_string = decoded.to_s
  117. should_encode = !decoded_string.ascii_only?
  118. if should_encode
  119. first = true
  120. words = decoded_string.split(/[ \t]/).map do |word|
  121. if first
  122. first = !first
  123. else
  124. word = " #{word}"
  125. end
  126. if !word.ascii_only?
  127. word
  128. else
  129. word.scan(/.{7}|.+$/)
  130. end
  131. end.flatten
  132. else
  133. words = decoded_string.split(/[ \t]/)
  134. end
  135. folded_lines = []
  136. while !words.empty?
  137. limit = 78 - prepend
  138. limit = limit - 7 - encoding.length if should_encode
  139. line = String.new
  140. first_word = true
  141. while !words.empty?
  142. break unless word = words.first.dup
  143. # Convert on 1.9+ only since we aren't sure of the current
  144. # charset encoding on 1.8. We'd need to track internal/external
  145. # charset on each field.
  146. if charset && word.respond_to?(:encoding)
  147. word = Encodings.transcode_charset(word, word.encoding, charset)
  148. end
  149. word = encode(word) if should_encode
  150. word = encode_crlf(word)
  151. # Skip to next line if we're going to go past the limit
  152. # Unless this is the first word, in which case we're going to add it anyway
  153. # Note: This means that a word that's longer than 998 characters is going to break the spec. Please fix if this is a problem for you.
  154. # (The fix, it seems, would be to use encoded-word encoding on it, because that way you can break it across multiple lines and
  155. # the linebreak will be ignored)
  156. break if !line.empty? && (line.length + word.length + 1 > limit)
  157. # Remove the word from the queue ...
  158. words.shift
  159. # Add word separator
  160. if first_word
  161. first_word = false
  162. else
  163. line << " " if !should_encode
  164. end
  165. # ... add it in encoded form to the current line
  166. line << word
  167. end
  168. # Encode the line if necessary
  169. line = "=?#{encoding}?Q?#{line}?=" if should_encode
  170. # Add the line to the output and reset the prepend
  171. folded_lines << line
  172. prepend = 0
  173. end
  174. folded_lines
  175. end
  176. 1 def encode(value)
  177. value = [value].pack(CAPITAL_M).gsub(EQUAL_LF, EMPTY)
  178. value.gsub!(/"/, '=22')
  179. value.gsub!(/\(/, '=28')
  180. value.gsub!(/\)/, '=29')
  181. value.gsub!(/\?/, '=3F')
  182. value.gsub!(/_/, '=5F')
  183. value.gsub!(/ /, '_')
  184. value
  185. end
  186. 1 def encode_crlf(value)
  187. value.gsub!(CR, CR_ENCODED)
  188. value.gsub!(LF, LF_ENCODED)
  189. value
  190. end
  191. 1 def normalized_encoding
  192. encoding = charset.to_s.upcase.gsub('_', '-')
  193. encoding = 'UTF-8' if encoding == 'UTF8' # Ruby 1.8.x and $KCODE == 'u'
  194. encoding
  195. end
  196. end
  197. end

target/rubygems/gems/mail-2.7.1/lib/mail/header.rb

35.48% lines covered

93 relevant lines. 33 lines covered and 60 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. 1 module Mail
  4. # Provides access to a header object.
  5. #
  6. # ===Per RFC2822
  7. #
  8. # 2.2. Header Fields
  9. #
  10. # Header fields are lines composed of a field name, followed by a colon
  11. # (":"), followed by a field body, and terminated by CRLF. A field
  12. # name MUST be composed of printable US-ASCII characters (i.e.,
  13. # characters that have values between 33 and 126, inclusive), except
  14. # colon. A field body may be composed of any US-ASCII characters,
  15. # except for CR and LF. However, a field body may contain CRLF when
  16. # used in header "folding" and "unfolding" as described in section
  17. # 2.2.3. All field bodies MUST conform to the syntax described in
  18. # sections 3 and 4 of this standard.
  19. 1 class Header
  20. 1 include Constants
  21. 1 include Utilities
  22. 1 include Enumerable
  23. 1 @@maximum_amount = 1000
  24. # Large amount of headers in Email might create extra high CPU load
  25. # Use this parameter to limit number of headers that will be parsed by
  26. # mail library.
  27. # Default: 1000
  28. 1 def self.maximum_amount
  29. @@maximum_amount
  30. end
  31. 1 def self.maximum_amount=(value)
  32. @@maximum_amount = value
  33. end
  34. # Creates a new header object.
  35. #
  36. # Accepts raw text or nothing. If given raw text will attempt to parse
  37. # it and split it into the various fields, instantiating each field as
  38. # it goes.
  39. #
  40. # If it finds a field that should be a structured field (such as content
  41. # type), but it fails to parse it, it will simply make it an unstructured
  42. # field and leave it alone. This will mean that the data is preserved but
  43. # no automatic processing of that field will happen. If you find one of
  44. # these cases, please make a patch and send it in, or at the least, send
  45. # me the example so we can fix it.
  46. 1 def initialize(header_text = nil, charset = nil)
  47. @charset = charset
  48. self.raw_source = header_text
  49. split_header if header_text
  50. end
  51. 1 def initialize_copy(original)
  52. super
  53. @fields = @fields.dup
  54. @fields.map!(&:dup)
  55. end
  56. # The preserved raw source of the header as you passed it in, untouched
  57. # for your Regexing glory.
  58. 1 def raw_source
  59. @raw_source
  60. end
  61. # Returns an array of all the fields in the header in order that they
  62. # were read in.
  63. 1 def fields
  64. @fields ||= FieldList.new
  65. end
  66. # 3.6. Field definitions
  67. #
  68. # It is important to note that the header fields are not guaranteed to
  69. # be in a particular order. They may appear in any order, and they
  70. # have been known to be reordered occasionally when transported over
  71. # the Internet. However, for the purposes of this standard, header
  72. # fields SHOULD NOT be reordered when a message is transported or
  73. # transformed. More importantly, the trace header fields and resent
  74. # header fields MUST NOT be reordered, and SHOULD be kept in blocks
  75. # prepended to the message. See sections 3.6.6 and 3.6.7 for more
  76. # information.
  77. #
  78. # Populates the fields container with Field objects in the order it
  79. # receives them in.
  80. #
  81. # Acceps an array of field string values, for example:
  82. #
  83. # h = Header.new
  84. # h.fields = ['From: mikel@me.com', 'To: bob@you.com']
  85. 1 def fields=(unfolded_fields)
  86. @fields = Mail::FieldList.new
  87. Kernel.warn "WARNING: More than #{self.class.maximum_amount} header fields; only using the first #{self.class.maximum_amount} and ignoring the rest" if unfolded_fields.length > self.class.maximum_amount
  88. unfolded_fields[0..(self.class.maximum_amount-1)].each do |field|
  89. if field = Field.parse(field, charset)
  90. if limited_field?(field.name) && (selected = select_field_for(field.name)) && selected.any?
  91. selected.first.update(field.name, field.value)
  92. else
  93. @fields << field
  94. end
  95. end
  96. end
  97. end
  98. 1 def errors
  99. @fields.map(&:errors).flatten(1)
  100. end
  101. # 3.6. Field definitions
  102. #
  103. # The following table indicates limits on the number of times each
  104. # field may occur in a message header as well as any special
  105. # limitations on the use of those fields. An asterisk next to a value
  106. # in the minimum or maximum column indicates that a special restriction
  107. # appears in the Notes column.
  108. #
  109. # <snip table from 3.6>
  110. #
  111. # As per RFC, many fields can appear more than once, we will return a string
  112. # of the value if there is only one header, or if there is more than one
  113. # matching header, will return an array of values in order that they appear
  114. # in the header ordered from top to bottom.
  115. #
  116. # Example:
  117. #
  118. # h = Header.new
  119. # h.fields = ['To: mikel@me.com', 'X-Mail-SPAM: 15', 'X-Mail-SPAM: 20']
  120. # h['To'] #=> 'mikel@me.com'
  121. # h['X-Mail-SPAM'] #=> ['15', '20']
  122. 1 def [](name)
  123. name = dasherize(name)
  124. name.downcase!
  125. selected = select_field_for(name)
  126. case
  127. when selected.length > 1
  128. selected.map { |f| f }
  129. when !Utilities.blank?(selected)
  130. selected.first
  131. else
  132. nil
  133. end
  134. end
  135. # Sets the FIRST matching field in the header to passed value, or deletes
  136. # the FIRST field matched from the header if passed nil
  137. #
  138. # Example:
  139. #
  140. # h = Header.new
  141. # h.fields = ['To: mikel@me.com', 'X-Mail-SPAM: 15', 'X-Mail-SPAM: 20']
  142. # h['To'] = 'bob@you.com'
  143. # h['To'] #=> 'bob@you.com'
  144. # h['X-Mail-SPAM'] = '10000'
  145. # h['X-Mail-SPAM'] # => ['15', '20', '10000']
  146. # h['X-Mail-SPAM'] = nil
  147. # h['X-Mail-SPAM'] # => nil
  148. 1 def []=(name, value)
  149. name = dasherize(name)
  150. if name.include?(':')
  151. raise ArgumentError, "Header names may not contain a colon: #{name.inspect}"
  152. end
  153. fn = name.downcase
  154. selected = select_field_for(fn)
  155. case
  156. # User wants to delete the field
  157. when !Utilities.blank?(selected) && value == nil
  158. fields.delete_if { |f| selected.include?(f) }
  159. # User wants to change the field
  160. when !Utilities.blank?(selected) && limited_field?(fn)
  161. selected.first.update(fn, value)
  162. # User wants to create the field
  163. else
  164. # Need to insert in correct order for trace fields
  165. self.fields << Field.new(name.to_s, value, charset)
  166. end
  167. if dasherize(fn) == "content-type"
  168. # Update charset if specified in Content-Type
  169. params = self[:content_type].parameters rescue nil
  170. @charset = params[:charset] if params && params[:charset]
  171. end
  172. end
  173. 1 def charset
  174. @charset
  175. end
  176. 1 def charset=(val)
  177. params = self[:content_type].parameters rescue nil
  178. if params
  179. params[:charset] = val
  180. end
  181. @charset = val
  182. end
  183. 1 LIMITED_FIELDS = %w[ date from sender reply-to to cc bcc
  184. message-id in-reply-to references subject
  185. return-path content-type mime-version
  186. content-transfer-encoding content-description
  187. content-id content-disposition content-location]
  188. 1 def encoded
  189. buffer = String.new
  190. buffer.force_encoding('us-ascii') if buffer.respond_to?(:force_encoding)
  191. fields.each do |field|
  192. buffer << field.encoded
  193. end
  194. buffer
  195. end
  196. 1 def to_s
  197. encoded
  198. end
  199. 1 def decoded
  200. raise NoMethodError, 'Can not decode an entire header as there could be character set conflicts, try calling #decoded on the various fields.'
  201. end
  202. 1 def field_summary
  203. fields.map { |f| "<#{f.name}: #{f.value}>" }.join(", ")
  204. end
  205. # Returns true if the header has a Message-ID defined (empty or not)
  206. 1 def has_message_id?
  207. !fields.select { |f| f.responsible_for?('Message-ID') }.empty?
  208. end
  209. # Returns true if the header has a Content-ID defined (empty or not)
  210. 1 def has_content_id?
  211. !fields.select { |f| f.responsible_for?('Content-ID') }.empty?
  212. end
  213. # Returns true if the header has a Date defined (empty or not)
  214. 1 def has_date?
  215. !fields.select { |f| f.responsible_for?('Date') }.empty?
  216. end
  217. # Returns true if the header has a MIME version defined (empty or not)
  218. 1 def has_mime_version?
  219. !fields.select { |f| f.responsible_for?('Mime-Version') }.empty?
  220. end
  221. 1 private
  222. 1 def raw_source=(val)
  223. @raw_source = ::Mail::Utilities.to_crlf(val).lstrip
  224. end
  225. # Splits an unfolded and line break cleaned header into individual field
  226. # strings.
  227. 1 def split_header
  228. self.fields = raw_source.split(HEADER_SPLIT)
  229. end
  230. 1 def select_field_for(name)
  231. fields.select { |f| f.responsible_for?(name) }
  232. end
  233. 1 def limited_field?(name)
  234. LIMITED_FIELDS.include?(name.to_s.downcase)
  235. end
  236. # Enumerable support; yield each field in order to the block if there is one,
  237. # or return an Enumerator for them if there isn't.
  238. 1 def each( &block )
  239. return self.fields.each( &block ) if block
  240. self.fields.each
  241. end
  242. end
  243. end

target/rubygems/gems/mail-2.7.1/lib/mail/indifferent_hash.rb

53.57% lines covered

56 relevant lines. 30 lines covered and 26 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. # This is an almost cut and paste from ActiveSupport v3.0.6, copied in here so that Mail
  4. # itself does not depend on ActiveSupport to avoid versioning conflicts
  5. 1 module Mail
  6. 1 class IndifferentHash < Hash
  7. 1 def initialize(constructor = {})
  8. if constructor.is_a?(Hash)
  9. super()
  10. update(constructor)
  11. else
  12. super(constructor)
  13. end
  14. end
  15. 1 def default(key = nil)
  16. if key.is_a?(Symbol) && include?(key = key.to_s)
  17. self[key]
  18. else
  19. super
  20. end
  21. end
  22. 1 def self.new_from_hash_copying_default(hash)
  23. IndifferentHash.new(hash).tap do |new_hash|
  24. new_hash.default = hash.default
  25. end
  26. end
  27. 1 alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
  28. 1 alias_method :regular_update, :update unless method_defined?(:regular_update)
  29. # Assigns a new value to the hash:
  30. #
  31. # hash = HashWithIndifferentAccess.new
  32. # hash[:key] = "value"
  33. #
  34. 1 def []=(key, value)
  35. regular_writer(convert_key(key), convert_value(value))
  36. end
  37. 1 alias_method :store, :[]=
  38. # Updates the instantized hash with values from the second:
  39. #
  40. # hash_1 = HashWithIndifferentAccess.new
  41. # hash_1[:key] = "value"
  42. #
  43. # hash_2 = HashWithIndifferentAccess.new
  44. # hash_2[:key] = "New Value!"
  45. #
  46. # hash_1.update(hash_2) # => {"key"=>"New Value!"}
  47. #
  48. 1 def update(other_hash)
  49. other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
  50. self
  51. end
  52. 1 alias_method :merge!, :update
  53. # Checks the hash for a key matching the argument passed in:
  54. #
  55. # hash = HashWithIndifferentAccess.new
  56. # hash["key"] = "value"
  57. # hash.key? :key # => true
  58. # hash.key? "key" # => true
  59. #
  60. 1 def key?(key)
  61. super(convert_key(key))
  62. end
  63. 1 alias_method :include?, :key?
  64. 1 alias_method :has_key?, :key?
  65. 1 alias_method :member?, :key?
  66. # Fetches the value for the specified key, same as doing hash[key]
  67. 1 def fetch(key, *extras)
  68. super(convert_key(key), *extras)
  69. end
  70. # Returns an array of the values at the specified indices:
  71. #
  72. # hash = HashWithIndifferentAccess.new
  73. # hash[:a] = "x"
  74. # hash[:b] = "y"
  75. # hash.values_at("a", "b") # => ["x", "y"]
  76. #
  77. 1 def values_at(*indices)
  78. indices.collect {|key| self[convert_key(key)]}
  79. end
  80. # Returns an exact copy of the hash.
  81. 1 def dup
  82. IndifferentHash.new(self)
  83. end
  84. # Merges the instantized and the specified hashes together, giving precedence to the values from the second hash
  85. # Does not overwrite the existing hash.
  86. 1 def merge(hash)
  87. self.dup.update(hash)
  88. end
  89. # Performs the opposite of merge, with the keys and values from the first hash taking precedence over the second.
  90. # This overloaded definition prevents returning a regular hash, if reverse_merge is called on a HashWithDifferentAccess.
  91. 1 def reverse_merge(other_hash)
  92. super self.class.new_from_hash_copying_default(other_hash)
  93. end
  94. 1 def reverse_merge!(other_hash)
  95. replace(reverse_merge( other_hash ))
  96. end
  97. # Removes a specified key from the hash.
  98. 1 def delete(key)
  99. super(convert_key(key))
  100. end
  101. 1 def stringify_keys!; self end
  102. 1 def stringify_keys; dup end
  103. 1 def symbolize_keys; to_hash.symbolize_keys end
  104. 1 def to_options!; self end
  105. 1 def to_hash
  106. Hash.new(default).merge!(self)
  107. end
  108. 1 protected
  109. 1 def convert_key(key)
  110. key.kind_of?(Symbol) ? key.to_s : key
  111. end
  112. 1 def convert_value(value)
  113. if value.class == Hash
  114. self.class.new_from_hash_copying_default(value)
  115. elsif value.is_a?(Array)
  116. value.dup.replace(value.map { |e| convert_value(e) })
  117. else
  118. value
  119. end
  120. end
  121. end
  122. end

target/rubygems/gems/mail-2.7.1/lib/mail/mail.rb

51.67% lines covered

60 relevant lines. 31 lines covered and 29 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. 1 module Mail
  4. # Allows you to create a new Mail::Message object.
  5. #
  6. # You can make an email via passing a string or passing a block.
  7. #
  8. # For example, the following two examples will create the same email
  9. # message:
  10. #
  11. # Creating via a string:
  12. #
  13. # string = "To: mikel@test.lindsaar.net\r\n"
  14. # string << "From: bob@test.lindsaar.net\r\n"
  15. # string << "Subject: This is an email\r\n"
  16. # string << "\r\n"
  17. # string << "This is the body"
  18. # Mail.new(string)
  19. #
  20. # Or creating via a block:
  21. #
  22. # message = Mail.new do
  23. # to 'mikel@test.lindsaar.net'
  24. # from 'bob@test.lindsaar.net'
  25. # subject 'This is an email'
  26. # body 'This is the body'
  27. # end
  28. #
  29. # Or creating via a hash (or hash like object):
  30. #
  31. # message = Mail.new({:to => 'mikel@test.lindsaar.net',
  32. # 'from' => 'bob@test.lindsaar.net',
  33. # :subject => 'This is an email',
  34. # :body => 'This is the body' })
  35. #
  36. # Note, the hash keys can be strings or symbols, the passed in object
  37. # does not need to be a hash, it just needs to respond to :each_pair
  38. # and yield each key value pair.
  39. #
  40. # As a side note, you can also create a new email through creating
  41. # a Mail::Message object directly and then passing in values via string,
  42. # symbol or direct method calls. See Mail::Message for more information.
  43. #
  44. # mail = Mail.new
  45. # mail.to = 'mikel@test.lindsaar.net'
  46. # mail[:from] = 'bob@test.lindsaar.net'
  47. # mail['subject'] = 'This is an email'
  48. # mail.body = 'This is the body'
  49. 1 def self.new(*args, &block)
  50. Message.new(args, &block)
  51. end
  52. # Sets the default delivery method and retriever method for all new Mail objects.
  53. # The delivery_method and retriever_method default to :smtp and :pop3, with defaults
  54. # set.
  55. #
  56. # So sending a new email, if you have an SMTP server running on localhost is
  57. # as easy as:
  58. #
  59. # Mail.deliver do
  60. # to 'mikel@test.lindsaar.net'
  61. # from 'bob@test.lindsaar.net'
  62. # subject 'hi there!'
  63. # body 'this is a body'
  64. # end
  65. #
  66. # If you do not specify anything, you will get the following equivalent code set in
  67. # every new mail object:
  68. #
  69. # Mail.defaults do
  70. # delivery_method :smtp, { :address => "localhost",
  71. # :port => 25,
  72. # :domain => 'localhost.localdomain',
  73. # :user_name => nil,
  74. # :password => nil,
  75. # :authentication => nil,
  76. # :enable_starttls_auto => true }
  77. #
  78. # retriever_method :pop3, { :address => "localhost",
  79. # :port => 995,
  80. # :user_name => nil,
  81. # :password => nil,
  82. # :enable_ssl => true }
  83. # end
  84. #
  85. # Mail.delivery_method.new #=> Mail::SMTP instance
  86. # Mail.retriever_method.new #=> Mail::POP3 instance
  87. #
  88. # Each mail object inherits the default set in Mail.delivery_method, however, on
  89. # a per email basis, you can override the method:
  90. #
  91. # mail.delivery_method :smtp
  92. #
  93. # Or you can override the method and pass in settings:
  94. #
  95. # mail.delivery_method :smtp, :address => 'some.host'
  96. 1 def self.defaults(&block)
  97. Configuration.instance.instance_eval(&block)
  98. end
  99. # Returns the delivery method selected, defaults to an instance of Mail::SMTP
  100. 1 def self.delivery_method
  101. Configuration.instance.delivery_method
  102. end
  103. # Returns the retriever method selected, defaults to an instance of Mail::POP3
  104. 1 def self.retriever_method
  105. Configuration.instance.retriever_method
  106. end
  107. # Send an email using the default configuration. You do need to set a default
  108. # configuration first before you use self.deliver, if you don't, an appropriate
  109. # error will be raised telling you to.
  110. #
  111. # If you do not specify a delivery type, SMTP will be used.
  112. #
  113. # Mail.deliver do
  114. # to 'mikel@test.lindsaar.net'
  115. # from 'ada@test.lindsaar.net'
  116. # subject 'This is a test email'
  117. # body 'Not much to say here'
  118. # end
  119. #
  120. # You can also do:
  121. #
  122. # mail = Mail.read('email.eml')
  123. # mail.deliver!
  124. #
  125. # And your email object will be created and sent.
  126. 1 def self.deliver(*args, &block)
  127. mail = self.new(args, &block)
  128. mail.deliver
  129. mail
  130. end
  131. # Find emails from the default retriever
  132. # See Mail::Retriever for a complete documentation.
  133. 1 def self.find(*args, &block)
  134. retriever_method.find(*args, &block)
  135. end
  136. # Finds and then deletes retrieved emails from the default retriever
  137. # See Mail::Retriever for a complete documentation.
  138. 1 def self.find_and_delete(*args, &block)
  139. retriever_method.find_and_delete(*args, &block)
  140. end
  141. # Receive the first email(s) from the default retriever
  142. # See Mail::Retriever for a complete documentation.
  143. 1 def self.first(*args, &block)
  144. retriever_method.first(*args, &block)
  145. end
  146. # Receive the first email(s) from the default retriever
  147. # See Mail::Retriever for a complete documentation.
  148. 1 def self.last(*args, &block)
  149. retriever_method.last(*args, &block)
  150. end
  151. # Receive all emails from the default retriever
  152. # See Mail::Retriever for a complete documentation.
  153. 1 def self.all(*args, &block)
  154. retriever_method.all(*args, &block)
  155. end
  156. # Reads in an email message from a path and instantiates it as a new Mail::Message
  157. 1 def self.read(filename)
  158. self.new(File.open(filename, 'rb') { |f| f.read })
  159. end
  160. # Delete all emails from the default retriever
  161. # See Mail::Retriever for a complete documentation.
  162. 1 def self.delete_all(*args, &block)
  163. retriever_method.delete_all(*args, &block)
  164. end
  165. # Instantiates a new Mail::Message using a string
  166. 1 def Mail.read_from_string(mail_as_string)
  167. Mail.new(mail_as_string)
  168. end
  169. 1 def Mail.connection(&block)
  170. retriever_method.connection(&block)
  171. end
  172. # Initialize the observers and interceptors arrays
  173. 1 @@delivery_notification_observers = []
  174. 1 @@delivery_interceptors = []
  175. # You can register an object to be informed of every email that is sent through
  176. # this method.
  177. #
  178. # Your object needs to respond to a single method #delivered_email(mail)
  179. # which receives the email that is sent.
  180. 1 def self.register_observer(observer)
  181. unless @@delivery_notification_observers.include?(observer)
  182. @@delivery_notification_observers << observer
  183. end
  184. end
  185. # Unregister the given observer, allowing mail to resume operations
  186. # without it.
  187. 1 def self.unregister_observer(observer)
  188. @@delivery_notification_observers.delete(observer)
  189. end
  190. # You can register an object to be given every mail object that will be sent,
  191. # before it is sent. So if you want to add special headers or modify any
  192. # email that gets sent through the Mail library, you can do so.
  193. #
  194. # Your object needs to respond to a single method #delivering_email(mail)
  195. # which receives the email that is about to be sent. Make your modifications
  196. # directly to this object.
  197. 1 def self.register_interceptor(interceptor)
  198. unless @@delivery_interceptors.include?(interceptor)
  199. @@delivery_interceptors << interceptor
  200. end
  201. end
  202. # Unregister the given interceptor, allowing mail to resume operations
  203. # without it.
  204. 1 def self.unregister_interceptor(interceptor)
  205. @@delivery_interceptors.delete(interceptor)
  206. end
  207. 1 def self.inform_observers(mail)
  208. @@delivery_notification_observers.each do |observer|
  209. observer.delivered_email(mail)
  210. end
  211. end
  212. 1 def self.inform_interceptors(mail)
  213. @@delivery_interceptors.each do |interceptor|
  214. interceptor.delivering_email(mail)
  215. end
  216. end
  217. 1 protected
  218. 1 RANDOM_TAG='%x%x_%x%x%d%x'
  219. 1 def self.random_tag
  220. t = Time.now
  221. sprintf(RANDOM_TAG,
  222. t.to_i, t.tv_usec,
  223. $$, Thread.current.object_id.abs, self.uniq, rand(255))
  224. end
  225. 1 private
  226. 1 def self.something_random
  227. 1 (Thread.current.object_id * rand(255) / Time.now.to_f).to_s.slice(-3..-1).to_i
  228. end
  229. 1 def self.uniq
  230. @@uniq += 1
  231. end
  232. 1 @@uniq = self.something_random
  233. end

target/rubygems/gems/mail-2.7.1/lib/mail/matchers/attachment_matchers.rb

66.67% lines covered

15 relevant lines. 10 lines covered and 5 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Mail
  3. 1 module Matchers
  4. 1 def any_attachment
  5. AnyAttachmentMatcher.new
  6. end
  7. 1 def an_attachment_with_filename(filename)
  8. AttachmentFilenameMatcher.new(filename)
  9. end
  10. 1 class AnyAttachmentMatcher
  11. 1 def ===(other)
  12. other.attachment?
  13. end
  14. end
  15. 1 class AttachmentFilenameMatcher
  16. 1 attr_reader :filename
  17. 1 def initialize(filename)
  18. @filename = filename
  19. end
  20. 1 def ===(other)
  21. other.attachment? && other.filename == filename
  22. end
  23. end
  24. end
  25. end

target/rubygems/gems/mail-2.7.1/lib/mail/matchers/has_sent_mail.rb

32.2% lines covered

118 relevant lines. 38 lines covered and 80 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Mail
  3. 1 module Matchers
  4. 1 def have_sent_email
  5. HasSentEmailMatcher.new(self)
  6. end
  7. 1 class HasSentEmailMatcher
  8. 1 def initialize(_context)
  9. end
  10. 1 def matches?(subject)
  11. matching_deliveries = filter_matched_deliveries(Mail::TestMailer.deliveries)
  12. !(matching_deliveries.empty?)
  13. end
  14. 1 def from(sender)
  15. @sender = sender
  16. self
  17. end
  18. 1 def to(recipient_or_list)
  19. @recipients ||= []
  20. if recipient_or_list.kind_of?(Array)
  21. @recipients += recipient_or_list
  22. else
  23. @recipients << recipient_or_list
  24. end
  25. self
  26. end
  27. 1 def cc(recipient_or_list)
  28. @copy_recipients ||= []
  29. if recipient_or_list.kind_of?(Array)
  30. @copy_recipients += recipient_or_list
  31. else
  32. @copy_recipients << recipient_or_list
  33. end
  34. self
  35. end
  36. 1 def bcc(recipient_or_list)
  37. @blind_copy_recipients ||= []
  38. @blind_copy_recipients.concat(Array(recipient_or_list))
  39. self
  40. end
  41. 1 def with_attachments(attachments)
  42. @attachments ||= []
  43. @attachments.concat(Array(attachments))
  44. self
  45. end
  46. 1 def with_no_attachments
  47. @having_attachments = false
  48. self
  49. end
  50. 1 def with_any_attachments
  51. @having_attachments = true
  52. self
  53. end
  54. 1 def with_subject(subject)
  55. @subject = subject
  56. self
  57. end
  58. 1 def matching_subject(subject_matcher)
  59. @subject_matcher = subject_matcher
  60. self
  61. end
  62. 1 def with_body(body)
  63. @body = body
  64. self
  65. end
  66. 1 def matching_body(body_matcher)
  67. @body_matcher = body_matcher
  68. self
  69. end
  70. 1 def with_html(body)
  71. @html_part_body = body
  72. self
  73. end
  74. 1 def with_text(body)
  75. @text_part_body = body
  76. self
  77. end
  78. 1 def description
  79. result = "send a matching email"
  80. result
  81. end
  82. 1 def failure_message
  83. result = "Expected email to be sent "
  84. result += explain_expectations
  85. result += dump_deliveries
  86. result
  87. end
  88. 1 def failure_message_when_negated
  89. result = "Expected no email to be sent "
  90. result += explain_expectations
  91. result += dump_deliveries
  92. result
  93. end
  94. 1 protected
  95. 1 def filter_matched_deliveries(deliveries)
  96. candidate_deliveries = deliveries
  97. modifiers =
  98. %w(sender recipients copy_recipients blind_copy_recipients subject
  99. subject_matcher body body_matcher html_part_body text_part_body having_attachments attachments)
  100. modifiers.each do |modifier_name|
  101. next unless instance_variable_defined?("@#{modifier_name}")
  102. candidate_deliveries = candidate_deliveries.select{|matching_delivery| self.send("matches_on_#{modifier_name}?", matching_delivery)}
  103. end
  104. candidate_deliveries
  105. end
  106. 1 def matches_on_sender?(delivery)
  107. delivery.from.include?(@sender)
  108. end
  109. 1 def matches_on_recipients?(delivery)
  110. @recipients.all? {|recipient| delivery.to.include?(recipient) }
  111. end
  112. 1 def matches_on_copy_recipients?(delivery)
  113. @copy_recipients.all? {|recipient| delivery.cc.include?(recipient) }
  114. end
  115. 1 def matches_on_blind_copy_recipients?(delivery)
  116. @blind_copy_recipients.all? {|recipient| delivery.bcc.include?(recipient) }
  117. end
  118. 1 def matches_on_subject?(delivery)
  119. delivery.subject == @subject
  120. end
  121. 1 def matches_on_subject_matcher?(delivery)
  122. @subject_matcher.match delivery.subject
  123. end
  124. 1 def matches_on_having_attachments?(delivery)
  125. @having_attachments && delivery.attachments.any? ||
  126. (!@having_attachments && delivery.attachments.none?)
  127. end
  128. 1 def matches_on_attachments?(delivery)
  129. @attachments.each_with_index.inject( true ) do |sent_attachments, (attachment, index)|
  130. sent_attachments &&= (attachment === delivery.attachments[index])
  131. end
  132. end
  133. 1 def matches_on_body?(delivery)
  134. delivery.body == @body
  135. end
  136. 1 def matches_on_body_matcher?(delivery)
  137. @body_matcher.match delivery.body.raw_source
  138. end
  139. 1 def matches_on_html_part_body?(delivery)
  140. delivery.html_part.body == @html_part_body
  141. end
  142. 1 def matches_on_text_part_body?(delivery)
  143. delivery.text_part.body == @text_part_body
  144. end
  145. 1 def explain_expectations
  146. result = ''
  147. result += "from #{@sender} " if instance_variable_defined?('@sender')
  148. result += "to #{@recipients.inspect} " if instance_variable_defined?('@recipients')
  149. result += "cc #{@copy_recipients.inspect} " if instance_variable_defined?('@copy_recipients')
  150. result += "bcc #{@blind_copy_recipients.inspect} " if instance_variable_defined?('@blind_copy_recipients')
  151. result += "with subject \"#{@subject}\" " if instance_variable_defined?('@subject')
  152. result += "with subject matching \"#{@subject_matcher}\" " if instance_variable_defined?('@subject_matcher')
  153. result += "with body \"#{@body}\" " if instance_variable_defined?('@body')
  154. result += "with body matching \"#{@body_matcher}\" " if instance_variable_defined?('@body_matcher')
  155. result += "with a text part matching \"#{@text_part_body}\" " if instance_variable_defined?('@text_part_body')
  156. result += "with an HTML part matching \"#{@html_part_body}\" " if instance_variable_defined?('@html_part_body')
  157. result
  158. end
  159. 1 def dump_deliveries
  160. "(actual deliveries: " + Mail::TestMailer.deliveries.inspect + ")"
  161. end
  162. end
  163. end
  164. end

target/rubygems/gems/mail-2.7.1/lib/mail/message.rb

30.83% lines covered

639 relevant lines. 197 lines covered and 442 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. 1 require "yaml"
  4. 1 module Mail
  5. # The Message class provides a single point of access to all things to do with an
  6. # email message.
  7. #
  8. # You create a new email message by calling the Mail::Message.new method, or just
  9. # Mail.new
  10. #
  11. # A Message object by default has the following objects inside it:
  12. #
  13. # * A Header object which contains all information and settings of the header of the email
  14. # * Body object which contains all parts of the email that are not part of the header, this
  15. # includes any attachments, body text, MIME parts etc.
  16. #
  17. # ==Per RFC2822
  18. #
  19. # 2.1. General Description
  20. #
  21. # At the most basic level, a message is a series of characters. A
  22. # message that is conformant with this standard is comprised of
  23. # characters with values in the range 1 through 127 and interpreted as
  24. # US-ASCII characters [ASCII]. For brevity, this document sometimes
  25. # refers to this range of characters as simply "US-ASCII characters".
  26. #
  27. # Note: This standard specifies that messages are made up of characters
  28. # in the US-ASCII range of 1 through 127. There are other documents,
  29. # specifically the MIME document series [RFC2045, RFC2046, RFC2047,
  30. # RFC2048, RFC2049], that extend this standard to allow for values
  31. # outside of that range. Discussion of those mechanisms is not within
  32. # the scope of this standard.
  33. #
  34. # Messages are divided into lines of characters. A line is a series of
  35. # characters that is delimited with the two characters carriage-return
  36. # and line-feed; that is, the carriage return (CR) character (ASCII
  37. # value 13) followed immediately by the line feed (LF) character (ASCII
  38. # value 10). (The carriage-return/line-feed pair is usually written in
  39. # this document as "CRLF".)
  40. #
  41. # A message consists of header fields (collectively called "the header
  42. # of the message") followed, optionally, by a body. The header is a
  43. # sequence of lines of characters with special syntax as defined in
  44. # this standard. The body is simply a sequence of characters that
  45. # follows the header and is separated from the header by an empty line
  46. # (i.e., a line with nothing preceding the CRLF).
  47. 1 class Message
  48. 1 include Constants
  49. 1 include Utilities
  50. # ==Making an email
  51. #
  52. # You can make an new mail object via a block, passing a string, file or direct assignment.
  53. #
  54. # ===Making an email via a block
  55. #
  56. # mail = Mail.new do |m|
  57. # m.from 'mikel@test.lindsaar.net'
  58. # m.to 'you@test.lindsaar.net'
  59. # m.subject 'This is a test email'
  60. # m.body File.read('body.txt')
  61. # end
  62. #
  63. # mail.to_s #=> "From: mikel@test.lindsaar.net\r\nTo: you@...
  64. #
  65. # If may also pass a block with no arguments, in which case it will
  66. # be evaluated in the scope of the new message instance:
  67. #
  68. # mail = Mail.new do
  69. # from 'mikel@test.lindsaar.net'
  70. # # ���
  71. # end
  72. #
  73. # ===Making an email via passing a string
  74. #
  75. # mail = Mail.new("To: mikel@test.lindsaar.net\r\nSubject: Hello\r\n\r\nHi there!")
  76. # mail.body.to_s #=> 'Hi there!'
  77. # mail.subject #=> 'Hello'
  78. # mail.to #=> 'mikel@test.lindsaar.net'
  79. #
  80. # ===Making an email from a file
  81. #
  82. # mail = Mail.read('path/to/file.eml')
  83. # mail.body.to_s #=> 'Hi there!'
  84. # mail.subject #=> 'Hello'
  85. # mail.to #=> 'mikel@test.lindsaar.net'
  86. #
  87. # ===Making an email via assignment
  88. #
  89. # You can assign values to a mail object via four approaches:
  90. #
  91. # * Message#field_name=(value)
  92. # * Message#field_name(value)
  93. # * Message#['field_name']=(value)
  94. # * Message#[:field_name]=(value)
  95. #
  96. # Examples:
  97. #
  98. # mail = Mail.new
  99. # mail['from'] = 'mikel@test.lindsaar.net'
  100. # mail[:to] = 'you@test.lindsaar.net'
  101. # mail.subject 'This is a test email'
  102. # mail.body = 'This is a body'
  103. #
  104. # mail.to_s #=> "From: mikel@test.lindsaar.net\r\nTo: you@...
  105. #
  106. 1 def initialize(*args, &block)
  107. @body = nil
  108. @body_raw = nil
  109. @separate_parts = false
  110. @text_part = nil
  111. @html_part = nil
  112. @errors = nil
  113. @header = nil
  114. @charset = self.class.default_charset
  115. @defaulted_charset = true
  116. @smtp_envelope_from = nil
  117. @smtp_envelope_to = nil
  118. @perform_deliveries = true
  119. @raise_delivery_errors = true
  120. @delivery_handler = nil
  121. @delivery_method = Mail.delivery_method.dup
  122. @transport_encoding = Mail::Encodings.get_encoding('7bit')
  123. @mark_for_delete = false
  124. if args.flatten.first.respond_to?(:each_pair)
  125. init_with_hash(args.flatten.first)
  126. else
  127. init_with_string(args.flatten[0].to_s)
  128. end
  129. # Support both builder styles:
  130. #
  131. # Mail.new do
  132. # to 'recipient@example.com'
  133. # end
  134. #
  135. # and
  136. #
  137. # Mail.new do |m|
  138. # m.to 'recipient@example.com'
  139. # end
  140. if block_given?
  141. if block.arity.zero? || (RUBY_VERSION < '1.9' && block.arity < 1)
  142. instance_eval(&block)
  143. else
  144. yield self
  145. end
  146. end
  147. self
  148. end
  149. # If you assign a delivery handler, mail will call :deliver_mail on the
  150. # object you assign to delivery_handler, it will pass itself as the
  151. # single argument.
  152. #
  153. # If you define a delivery_handler, then you are responsible for the
  154. # following actions in the delivery cycle:
  155. #
  156. # * Appending the mail object to Mail.deliveries as you see fit.
  157. # * Checking the mail.perform_deliveries flag to decide if you should
  158. # actually call :deliver! the mail object or not.
  159. # * Checking the mail.raise_delivery_errors flag to decide if you
  160. # should raise delivery errors if they occur.
  161. # * Actually calling :deliver! (with the bang) on the mail object to
  162. # get it to deliver itself.
  163. #
  164. # A simplest implementation of a delivery_handler would be
  165. #
  166. # class MyObject
  167. #
  168. # def initialize
  169. # @mail = Mail.new('To: mikel@test.lindsaar.net')
  170. # @mail.delivery_handler = self
  171. # end
  172. #
  173. # attr_accessor :mail
  174. #
  175. # def deliver_mail(mail)
  176. # yield
  177. # end
  178. # end
  179. #
  180. # Then doing:
  181. #
  182. # obj = MyObject.new
  183. # obj.mail.deliver
  184. #
  185. # Would cause Mail to call obj.deliver_mail passing itself as a parameter,
  186. # which then can just yield and let Mail do its own private do_delivery
  187. # method.
  188. 1 attr_accessor :delivery_handler
  189. # If set to false, mail will go through the motions of doing a delivery,
  190. # but not actually call the delivery method or append the mail object to
  191. # the Mail.deliveries collection. Useful for testing.
  192. #
  193. # Mail.deliveries.size #=> 0
  194. # mail.delivery_method :smtp
  195. # mail.perform_deliveries = false
  196. # mail.deliver # Mail::SMTP not called here
  197. # Mail.deliveries.size #=> 0
  198. #
  199. # If you want to test and query the Mail.deliveries collection to see what
  200. # mail you sent, you should set perform_deliveries to true and use
  201. # the :test mail delivery_method:
  202. #
  203. # Mail.deliveries.size #=> 0
  204. # mail.delivery_method :test
  205. # mail.perform_deliveries = true
  206. # mail.deliver
  207. # Mail.deliveries.size #=> 1
  208. #
  209. # This setting is ignored by mail (though still available as a flag) if you
  210. # define a delivery_handler
  211. 1 attr_accessor :perform_deliveries
  212. # If set to false, mail will silently catch and ignore any exceptions
  213. # raised through attempting to deliver an email.
  214. #
  215. # This setting is ignored by mail (though still available as a flag) if you
  216. # define a delivery_handler
  217. 1 attr_accessor :raise_delivery_errors
  218. 1 def self.default_charset; @@default_charset; end
  219. 2 def self.default_charset=(charset); @@default_charset = charset; end
  220. 1 self.default_charset = 'UTF-8'
  221. 1 def register_for_delivery_notification(observer)
  222. warn("Message#register_for_delivery_notification is deprecated, please call Mail.register_observer instead")
  223. Mail.register_observer(observer)
  224. end
  225. 1 def inform_observers
  226. Mail.inform_observers(self)
  227. end
  228. 1 def inform_interceptors
  229. Mail.inform_interceptors(self)
  230. end
  231. # Delivers a mail object.
  232. #
  233. # Examples:
  234. #
  235. # mail = Mail.read('file.eml')
  236. # mail.deliver
  237. 1 def deliver
  238. inform_interceptors
  239. if delivery_handler
  240. delivery_handler.deliver_mail(self) { do_delivery }
  241. else
  242. do_delivery
  243. end
  244. inform_observers
  245. self
  246. end
  247. # This method bypasses checking perform_deliveries and raise_delivery_errors,
  248. # so use with caution.
  249. #
  250. # It still however fires off the interceptors and calls the observers callbacks if they are defined.
  251. #
  252. # Returns self
  253. 1 def deliver!
  254. inform_interceptors
  255. response = delivery_method.deliver!(self)
  256. inform_observers
  257. delivery_method.settings[:return_response] ? response : self
  258. end
  259. 1 def delivery_method(method = nil, settings = {})
  260. unless method
  261. @delivery_method
  262. else
  263. @delivery_method = Configuration.instance.lookup_delivery_method(method).new(settings)
  264. end
  265. end
  266. 1 def reply(*args, &block)
  267. self.class.new.tap do |reply|
  268. if message_id
  269. bracketed_message_id = "<#{message_id}>"
  270. reply.in_reply_to = bracketed_message_id
  271. if !references.nil?
  272. refs = [references].flatten.map { |r| "<#{r}>" }
  273. refs << bracketed_message_id
  274. reply.references = refs.join(' ')
  275. elsif !in_reply_to.nil? && !in_reply_to.kind_of?(Array)
  276. reply.references = "<#{in_reply_to}> #{bracketed_message_id}"
  277. end
  278. reply.references ||= bracketed_message_id
  279. end
  280. if subject
  281. reply.subject = subject =~ /^Re:/i ? subject : "RE: #{subject}"
  282. end
  283. if reply_to || from
  284. reply.to = self[reply_to ? :reply_to : :from].to_s
  285. end
  286. if to
  287. reply.from = self[:to].formatted.first.to_s
  288. end
  289. unless args.empty?
  290. if args.flatten.first.respond_to?(:each_pair)
  291. reply.send(:init_with_hash, args.flatten.first)
  292. else
  293. reply.send(:init_with_string, args.flatten[0].to_s.strip)
  294. end
  295. end
  296. if block_given?
  297. reply.instance_eval(&block)
  298. end
  299. end
  300. end
  301. # Provides the operator needed for sort et al.
  302. #
  303. # Compares this mail object with another mail object, this is done by date, so an
  304. # email that is older than another will appear first.
  305. #
  306. # Example:
  307. #
  308. # mail1 = Mail.new do
  309. # date(Time.now)
  310. # end
  311. # mail2 = Mail.new do
  312. # date(Time.now - 86400) # 1 day older
  313. # end
  314. # [mail2, mail1].sort #=> [mail2, mail1]
  315. 1 def <=>(other)
  316. if other.nil?
  317. 1
  318. else
  319. self.date <=> other.date
  320. end
  321. end
  322. # Two emails are the same if they have the same fields and body contents. One
  323. # gotcha here is that Mail will insert Message-IDs when calling encoded, so doing
  324. # mail1.encoded == mail2.encoded is most probably not going to return what you think
  325. # as the assigned Message-IDs by Mail (if not already defined as the same) will ensure
  326. # that the two objects are unique, and this comparison will ALWAYS return false.
  327. #
  328. # So the == operator has been defined like so: Two messages are the same if they have
  329. # the same content, ignoring the Message-ID field, unless BOTH emails have a defined and
  330. # different Message-ID value, then they are false.
  331. #
  332. # So, in practice the == operator works like this:
  333. #
  334. # m1 = Mail.new("Subject: Hello\r\n\r\nHello")
  335. # m2 = Mail.new("Subject: Hello\r\n\r\nHello")
  336. # m1 == m2 #=> true
  337. #
  338. # m1 = Mail.new("Subject: Hello\r\n\r\nHello")
  339. # m2 = Mail.new("Message-ID: <1234@test>\r\nSubject: Hello\r\n\r\nHello")
  340. # m1 == m2 #=> true
  341. #
  342. # m1 = Mail.new("Message-ID: <1234@test>\r\nSubject: Hello\r\n\r\nHello")
  343. # m2 = Mail.new("Subject: Hello\r\n\r\nHello")
  344. # m1 == m2 #=> true
  345. #
  346. # m1 = Mail.new("Message-ID: <1234@test>\r\nSubject: Hello\r\n\r\nHello")
  347. # m2 = Mail.new("Message-ID: <1234@test>\r\nSubject: Hello\r\n\r\nHello")
  348. # m1 == m2 #=> true
  349. #
  350. # m1 = Mail.new("Message-ID: <1234@test>\r\nSubject: Hello\r\n\r\nHello")
  351. # m2 = Mail.new("Message-ID: <DIFFERENT@test>\r\nSubject: Hello\r\n\r\nHello")
  352. # m1 == m2 #=> false
  353. 1 def ==(other)
  354. return false unless other.respond_to?(:encoded)
  355. if self.message_id && other.message_id
  356. self.encoded == other.encoded
  357. else
  358. dup.tap { |m| m.message_id = '<temp@test>' }.encoded ==
  359. other.dup.tap { |m| m.message_id = '<temp@test>' }.encoded
  360. end
  361. end
  362. 1 def initialize_copy(original)
  363. super
  364. @header = @header.dup
  365. end
  366. # Provides access to the raw source of the message as it was when it
  367. # was instantiated. This is set at initialization and so is untouched
  368. # by the parsers or decoder / encoders
  369. #
  370. # Example:
  371. #
  372. # mail = Mail.new('This is an invalid email message')
  373. # mail.raw_source #=> "This is an invalid email message"
  374. 1 def raw_source
  375. @raw_source
  376. end
  377. # Sets the envelope from for the email
  378. 1 def set_envelope( val )
  379. @raw_envelope = val
  380. @envelope = Mail::Envelope.new( val )
  381. end
  382. # The raw_envelope is the From mikel@test.lindsaar.net Mon May 2 16:07:05 2009
  383. # type field that you can see at the top of any email that has come
  384. # from a mailbox
  385. 1 def raw_envelope
  386. @raw_envelope
  387. end
  388. 1 def envelope_from
  389. @envelope ? @envelope.from : nil
  390. end
  391. 1 def envelope_date
  392. @envelope ? @envelope.date : nil
  393. end
  394. # Sets the header of the message object.
  395. #
  396. # Example:
  397. #
  398. # mail.header = 'To: mikel@test.lindsaar.net\r\nFrom: Bob@bob.com'
  399. # mail.header #=> <#Mail::Header
  400. 1 def header=(value)
  401. @header = Mail::Header.new(value, charset)
  402. end
  403. # Returns the header object of the message object. Or, if passed
  404. # a parameter sets the value.
  405. #
  406. # Example:
  407. #
  408. # mail = Mail::Message.new('To: mikel\r\nFrom: you')
  409. # mail.header #=> #<Mail::Header:0x13ce14 @raw_source="To: mikel\r\nFr...
  410. #
  411. # mail.header #=> nil
  412. # mail.header 'To: mikel\r\nFrom: you'
  413. # mail.header #=> #<Mail::Header:0x13ce14 @raw_source="To: mikel\r\nFr...
  414. 1 def header(value = nil)
  415. value ? self.header = value : @header
  416. end
  417. # Provides a way to set custom headers, by passing in a hash
  418. 1 def headers(hash = {})
  419. hash.each_pair do |k,v|
  420. header[k] = v
  421. end
  422. end
  423. # Returns a list of parser errors on the header, each field that had an error
  424. # will be reparsed as an unstructured field to preserve the data inside, but
  425. # will not be used for further processing.
  426. #
  427. # It returns a nested array of [field_name, value, original_error_message]
  428. # per error found.
  429. #
  430. # Example:
  431. #
  432. # message = Mail.new("Content-Transfer-Encoding: weirdo\r\n")
  433. # message.errors.size #=> 1
  434. # message.errors.first[0] #=> "Content-Transfer-Encoding"
  435. # message.errors.first[1] #=> "weirdo"
  436. # message.errors.first[3] #=> <The original error message exception>
  437. #
  438. # This is a good first defence on detecting spam by the way. Some spammers send
  439. # invalid emails to try and get email parsers to give up parsing them.
  440. 1 def errors
  441. header.errors
  442. end
  443. # Returns the Bcc value of the mail object as an array of strings of
  444. # address specs.
  445. #
  446. # Example:
  447. #
  448. # mail.bcc = 'Mikel <mikel@test.lindsaar.net>'
  449. # mail.bcc #=> ['mikel@test.lindsaar.net']
  450. # mail.bcc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  451. # mail.bcc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  452. #
  453. # Also allows you to set the value by passing a value as a parameter
  454. #
  455. # Example:
  456. #
  457. # mail.bcc 'Mikel <mikel@test.lindsaar.net>'
  458. # mail.bcc #=> ['mikel@test.lindsaar.net']
  459. #
  460. # Additionally, you can append new addresses to the returned Array like
  461. # object.
  462. #
  463. # Example:
  464. #
  465. # mail.bcc 'Mikel <mikel@test.lindsaar.net>'
  466. # mail.bcc << 'ada@test.lindsaar.net'
  467. # mail.bcc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  468. 1 def bcc( val = nil )
  469. default :bcc, val
  470. end
  471. # Sets the Bcc value of the mail object, pass in a string of the field
  472. #
  473. # Example:
  474. #
  475. # mail.bcc = 'Mikel <mikel@test.lindsaar.net>'
  476. # mail.bcc #=> ['mikel@test.lindsaar.net']
  477. # mail.bcc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  478. # mail.bcc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  479. 1 def bcc=( val )
  480. header[:bcc] = val
  481. end
  482. # Returns the Cc value of the mail object as an array of strings of
  483. # address specs.
  484. #
  485. # Example:
  486. #
  487. # mail.cc = 'Mikel <mikel@test.lindsaar.net>'
  488. # mail.cc #=> ['mikel@test.lindsaar.net']
  489. # mail.cc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  490. # mail.cc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  491. #
  492. # Also allows you to set the value by passing a value as a parameter
  493. #
  494. # Example:
  495. #
  496. # mail.cc 'Mikel <mikel@test.lindsaar.net>'
  497. # mail.cc #=> ['mikel@test.lindsaar.net']
  498. #
  499. # Additionally, you can append new addresses to the returned Array like
  500. # object.
  501. #
  502. # Example:
  503. #
  504. # mail.cc 'Mikel <mikel@test.lindsaar.net>'
  505. # mail.cc << 'ada@test.lindsaar.net'
  506. # mail.cc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  507. 1 def cc( val = nil )
  508. default :cc, val
  509. end
  510. # Sets the Cc value of the mail object, pass in a string of the field
  511. #
  512. # Example:
  513. #
  514. # mail.cc = 'Mikel <mikel@test.lindsaar.net>'
  515. # mail.cc #=> ['mikel@test.lindsaar.net']
  516. # mail.cc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  517. # mail.cc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  518. 1 def cc=( val )
  519. header[:cc] = val
  520. end
  521. 1 def comments( val = nil )
  522. default :comments, val
  523. end
  524. 1 def comments=( val )
  525. header[:comments] = val
  526. end
  527. 1 def content_description( val = nil )
  528. default :content_description, val
  529. end
  530. 1 def content_description=( val )
  531. header[:content_description] = val
  532. end
  533. 1 def content_disposition( val = nil )
  534. default :content_disposition, val
  535. end
  536. 1 def content_disposition=( val )
  537. header[:content_disposition] = val
  538. end
  539. 1 def content_id( val = nil )
  540. default :content_id, val
  541. end
  542. 1 def content_id=( val )
  543. header[:content_id] = val
  544. end
  545. 1 def content_location( val = nil )
  546. default :content_location, val
  547. end
  548. 1 def content_location=( val )
  549. header[:content_location] = val
  550. end
  551. 1 def content_transfer_encoding( val = nil )
  552. default :content_transfer_encoding, val
  553. end
  554. 1 def content_transfer_encoding=( val )
  555. header[:content_transfer_encoding] = val
  556. end
  557. 1 def content_type( val = nil )
  558. default :content_type, val
  559. end
  560. 1 def content_type=( val )
  561. header[:content_type] = val
  562. end
  563. 1 def date( val = nil )
  564. default :date, val
  565. end
  566. 1 def date=( val )
  567. header[:date] = val
  568. end
  569. 1 def transport_encoding( val = nil)
  570. if val
  571. self.transport_encoding = val
  572. else
  573. @transport_encoding
  574. end
  575. end
  576. 1 def transport_encoding=( val )
  577. @transport_encoding = Mail::Encodings.get_encoding(val)
  578. end
  579. # Returns the From value of the mail object as an array of strings of
  580. # address specs.
  581. #
  582. # Example:
  583. #
  584. # mail.from = 'Mikel <mikel@test.lindsaar.net>'
  585. # mail.from #=> ['mikel@test.lindsaar.net']
  586. # mail.from = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  587. # mail.from #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  588. #
  589. # Also allows you to set the value by passing a value as a parameter
  590. #
  591. # Example:
  592. #
  593. # mail.from 'Mikel <mikel@test.lindsaar.net>'
  594. # mail.from #=> ['mikel@test.lindsaar.net']
  595. #
  596. # Additionally, you can append new addresses to the returned Array like
  597. # object.
  598. #
  599. # Example:
  600. #
  601. # mail.from 'Mikel <mikel@test.lindsaar.net>'
  602. # mail.from << 'ada@test.lindsaar.net'
  603. # mail.from #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  604. 1 def from( val = nil )
  605. default :from, val
  606. end
  607. # Sets the From value of the mail object, pass in a string of the field
  608. #
  609. # Example:
  610. #
  611. # mail.from = 'Mikel <mikel@test.lindsaar.net>'
  612. # mail.from #=> ['mikel@test.lindsaar.net']
  613. # mail.from = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  614. # mail.from #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  615. 1 def from=( val )
  616. header[:from] = val
  617. end
  618. 1 def in_reply_to( val = nil )
  619. default :in_reply_to, val
  620. end
  621. 1 def in_reply_to=( val )
  622. header[:in_reply_to] = val
  623. end
  624. 1 def keywords( val = nil )
  625. default :keywords, val
  626. end
  627. 1 def keywords=( val )
  628. header[:keywords] = val
  629. end
  630. # Returns the Message-ID of the mail object. Note, per RFC 2822 the Message ID
  631. # consists of what is INSIDE the < > usually seen in the mail header, so this method
  632. # will return only what is inside.
  633. #
  634. # Example:
  635. #
  636. # mail.message_id = '<1234@message.id>'
  637. # mail.message_id #=> '1234@message.id'
  638. #
  639. # Also allows you to set the Message-ID by passing a string as a parameter
  640. #
  641. # mail.message_id '<1234@message.id>'
  642. # mail.message_id #=> '1234@message.id'
  643. 1 def message_id( val = nil )
  644. default :message_id, val
  645. end
  646. # Sets the Message-ID. Note, per RFC 2822 the Message ID consists of what is INSIDE
  647. # the < > usually seen in the mail header, so this method will return only what is inside.
  648. #
  649. # mail.message_id = '<1234@message.id>'
  650. # mail.message_id #=> '1234@message.id'
  651. 1 def message_id=( val )
  652. header[:message_id] = val
  653. end
  654. # Returns the MIME version of the email as a string
  655. #
  656. # Example:
  657. #
  658. # mail.mime_version = '1.0'
  659. # mail.mime_version #=> '1.0'
  660. #
  661. # Also allows you to set the MIME version by passing a string as a parameter.
  662. #
  663. # Example:
  664. #
  665. # mail.mime_version '1.0'
  666. # mail.mime_version #=> '1.0'
  667. 1 def mime_version( val = nil )
  668. default :mime_version, val
  669. end
  670. # Sets the MIME version of the email by accepting a string
  671. #
  672. # Example:
  673. #
  674. # mail.mime_version = '1.0'
  675. # mail.mime_version #=> '1.0'
  676. 1 def mime_version=( val )
  677. header[:mime_version] = val
  678. end
  679. 1 def received( val = nil )
  680. if val
  681. header[:received] = val
  682. else
  683. header[:received]
  684. end
  685. end
  686. 1 def received=( val )
  687. header[:received] = val
  688. end
  689. 1 def references( val = nil )
  690. default :references, val
  691. end
  692. 1 def references=( val )
  693. header[:references] = val
  694. end
  695. # Returns the Reply-To value of the mail object as an array of strings of
  696. # address specs.
  697. #
  698. # Example:
  699. #
  700. # mail.reply_to = 'Mikel <mikel@test.lindsaar.net>'
  701. # mail.reply_to #=> ['mikel@test.lindsaar.net']
  702. # mail.reply_to = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  703. # mail.reply_to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  704. #
  705. # Also allows you to set the value by passing a value as a parameter
  706. #
  707. # Example:
  708. #
  709. # mail.reply_to 'Mikel <mikel@test.lindsaar.net>'
  710. # mail.reply_to #=> ['mikel@test.lindsaar.net']
  711. #
  712. # Additionally, you can append new addresses to the returned Array like
  713. # object.
  714. #
  715. # Example:
  716. #
  717. # mail.reply_to 'Mikel <mikel@test.lindsaar.net>'
  718. # mail.reply_to << 'ada@test.lindsaar.net'
  719. # mail.reply_to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  720. 1 def reply_to( val = nil )
  721. default :reply_to, val
  722. end
  723. # Sets the Reply-To value of the mail object, pass in a string of the field
  724. #
  725. # Example:
  726. #
  727. # mail.reply_to = 'Mikel <mikel@test.lindsaar.net>'
  728. # mail.reply_to #=> ['mikel@test.lindsaar.net']
  729. # mail.reply_to = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  730. # mail.reply_to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  731. 1 def reply_to=( val )
  732. header[:reply_to] = val
  733. end
  734. # Returns the Resent-Bcc value of the mail object as an array of strings of
  735. # address specs.
  736. #
  737. # Example:
  738. #
  739. # mail.resent_bcc = 'Mikel <mikel@test.lindsaar.net>'
  740. # mail.resent_bcc #=> ['mikel@test.lindsaar.net']
  741. # mail.resent_bcc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  742. # mail.resent_bcc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  743. #
  744. # Also allows you to set the value by passing a value as a parameter
  745. #
  746. # Example:
  747. #
  748. # mail.resent_bcc 'Mikel <mikel@test.lindsaar.net>'
  749. # mail.resent_bcc #=> ['mikel@test.lindsaar.net']
  750. #
  751. # Additionally, you can append new addresses to the returned Array like
  752. # object.
  753. #
  754. # Example:
  755. #
  756. # mail.resent_bcc 'Mikel <mikel@test.lindsaar.net>'
  757. # mail.resent_bcc << 'ada@test.lindsaar.net'
  758. # mail.resent_bcc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  759. 1 def resent_bcc( val = nil )
  760. default :resent_bcc, val
  761. end
  762. # Sets the Resent-Bcc value of the mail object, pass in a string of the field
  763. #
  764. # Example:
  765. #
  766. # mail.resent_bcc = 'Mikel <mikel@test.lindsaar.net>'
  767. # mail.resent_bcc #=> ['mikel@test.lindsaar.net']
  768. # mail.resent_bcc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  769. # mail.resent_bcc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  770. 1 def resent_bcc=( val )
  771. header[:resent_bcc] = val
  772. end
  773. # Returns the Resent-Cc value of the mail object as an array of strings of
  774. # address specs.
  775. #
  776. # Example:
  777. #
  778. # mail.resent_cc = 'Mikel <mikel@test.lindsaar.net>'
  779. # mail.resent_cc #=> ['mikel@test.lindsaar.net']
  780. # mail.resent_cc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  781. # mail.resent_cc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  782. #
  783. # Also allows you to set the value by passing a value as a parameter
  784. #
  785. # Example:
  786. #
  787. # mail.resent_cc 'Mikel <mikel@test.lindsaar.net>'
  788. # mail.resent_cc #=> ['mikel@test.lindsaar.net']
  789. #
  790. # Additionally, you can append new addresses to the returned Array like
  791. # object.
  792. #
  793. # Example:
  794. #
  795. # mail.resent_cc 'Mikel <mikel@test.lindsaar.net>'
  796. # mail.resent_cc << 'ada@test.lindsaar.net'
  797. # mail.resent_cc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  798. 1 def resent_cc( val = nil )
  799. default :resent_cc, val
  800. end
  801. # Sets the Resent-Cc value of the mail object, pass in a string of the field
  802. #
  803. # Example:
  804. #
  805. # mail.resent_cc = 'Mikel <mikel@test.lindsaar.net>'
  806. # mail.resent_cc #=> ['mikel@test.lindsaar.net']
  807. # mail.resent_cc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  808. # mail.resent_cc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  809. 1 def resent_cc=( val )
  810. header[:resent_cc] = val
  811. end
  812. 1 def resent_date( val = nil )
  813. default :resent_date, val
  814. end
  815. 1 def resent_date=( val )
  816. header[:resent_date] = val
  817. end
  818. # Returns the Resent-From value of the mail object as an array of strings of
  819. # address specs.
  820. #
  821. # Example:
  822. #
  823. # mail.resent_from = 'Mikel <mikel@test.lindsaar.net>'
  824. # mail.resent_from #=> ['mikel@test.lindsaar.net']
  825. # mail.resent_from = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  826. # mail.resent_from #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  827. #
  828. # Also allows you to set the value by passing a value as a parameter
  829. #
  830. # Example:
  831. #
  832. # mail.resent_from ['Mikel <mikel@test.lindsaar.net>']
  833. # mail.resent_from #=> 'mikel@test.lindsaar.net'
  834. #
  835. # Additionally, you can append new addresses to the returned Array like
  836. # object.
  837. #
  838. # Example:
  839. #
  840. # mail.resent_from 'Mikel <mikel@test.lindsaar.net>'
  841. # mail.resent_from << 'ada@test.lindsaar.net'
  842. # mail.resent_from #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  843. 1 def resent_from( val = nil )
  844. default :resent_from, val
  845. end
  846. # Sets the Resent-From value of the mail object, pass in a string of the field
  847. #
  848. # Example:
  849. #
  850. # mail.resent_from = 'Mikel <mikel@test.lindsaar.net>'
  851. # mail.resent_from #=> ['mikel@test.lindsaar.net']
  852. # mail.resent_from = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  853. # mail.resent_from #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  854. 1 def resent_from=( val )
  855. header[:resent_from] = val
  856. end
  857. 1 def resent_message_id( val = nil )
  858. default :resent_message_id, val
  859. end
  860. 1 def resent_message_id=( val )
  861. header[:resent_message_id] = val
  862. end
  863. # Returns the Resent-Sender value of the mail object, as a single string of an address
  864. # spec. A sender per RFC 2822 must be a single address, so you can not append to
  865. # this address.
  866. #
  867. # Example:
  868. #
  869. # mail.resent_sender = 'Mikel <mikel@test.lindsaar.net>'
  870. # mail.resent_sender #=> 'mikel@test.lindsaar.net'
  871. #
  872. # Also allows you to set the value by passing a value as a parameter
  873. #
  874. # Example:
  875. #
  876. # mail.resent_sender 'Mikel <mikel@test.lindsaar.net>'
  877. # mail.resent_sender #=> 'mikel@test.lindsaar.net'
  878. 1 def resent_sender( val = nil )
  879. default :resent_sender, val
  880. end
  881. # Sets the Resent-Sender value of the mail object, pass in a string of the field
  882. #
  883. # Example:
  884. #
  885. # mail.resent_sender = 'Mikel <mikel@test.lindsaar.net>'
  886. # mail.resent_sender #=> 'mikel@test.lindsaar.net'
  887. 1 def resent_sender=( val )
  888. header[:resent_sender] = val
  889. end
  890. # Returns the Resent-To value of the mail object as an array of strings of
  891. # address specs.
  892. #
  893. # Example:
  894. #
  895. # mail.resent_to = 'Mikel <mikel@test.lindsaar.net>'
  896. # mail.resent_to #=> ['mikel@test.lindsaar.net']
  897. # mail.resent_to = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  898. # mail.resent_to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  899. #
  900. # Also allows you to set the value by passing a value as a parameter
  901. #
  902. # Example:
  903. #
  904. # mail.resent_to 'Mikel <mikel@test.lindsaar.net>'
  905. # mail.resent_to #=> ['mikel@test.lindsaar.net']
  906. #
  907. # Additionally, you can append new addresses to the returned Array like
  908. # object.
  909. #
  910. # Example:
  911. #
  912. # mail.resent_to 'Mikel <mikel@test.lindsaar.net>'
  913. # mail.resent_to << 'ada@test.lindsaar.net'
  914. # mail.resent_to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  915. 1 def resent_to( val = nil )
  916. default :resent_to, val
  917. end
  918. # Sets the Resent-To value of the mail object, pass in a string of the field
  919. #
  920. # Example:
  921. #
  922. # mail.resent_to = 'Mikel <mikel@test.lindsaar.net>'
  923. # mail.resent_to #=> ['mikel@test.lindsaar.net']
  924. # mail.resent_to = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  925. # mail.resent_to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  926. 1 def resent_to=( val )
  927. header[:resent_to] = val
  928. end
  929. # Returns the return path of the mail object, or sets it if you pass a string
  930. 1 def return_path( val = nil )
  931. default :return_path, val
  932. end
  933. # Sets the return path of the object
  934. 1 def return_path=( val )
  935. header[:return_path] = val
  936. end
  937. # Returns the Sender value of the mail object, as a single string of an address
  938. # spec. A sender per RFC 2822 must be a single address.
  939. #
  940. # Example:
  941. #
  942. # mail.sender = 'Mikel <mikel@test.lindsaar.net>'
  943. # mail.sender #=> 'mikel@test.lindsaar.net'
  944. #
  945. # Also allows you to set the value by passing a value as a parameter
  946. #
  947. # Example:
  948. #
  949. # mail.sender 'Mikel <mikel@test.lindsaar.net>'
  950. # mail.sender #=> 'mikel@test.lindsaar.net'
  951. 1 def sender( val = nil )
  952. default :sender, val
  953. end
  954. # Sets the Sender value of the mail object, pass in a string of the field
  955. #
  956. # Example:
  957. #
  958. # mail.sender = 'Mikel <mikel@test.lindsaar.net>'
  959. # mail.sender #=> 'mikel@test.lindsaar.net'
  960. 1 def sender=( val )
  961. header[:sender] = val
  962. end
  963. # Returns the SMTP Envelope From value of the mail object, as a single
  964. # string of an address spec.
  965. #
  966. # Defaults to Return-Path, Sender, or the first From address.
  967. #
  968. # Example:
  969. #
  970. # mail.smtp_envelope_from = 'Mikel <mikel@test.lindsaar.net>'
  971. # mail.smtp_envelope_from #=> 'mikel@test.lindsaar.net'
  972. #
  973. # Also allows you to set the value by passing a value as a parameter
  974. #
  975. # Example:
  976. #
  977. # mail.smtp_envelope_from 'Mikel <mikel@test.lindsaar.net>'
  978. # mail.smtp_envelope_from #=> 'mikel@test.lindsaar.net'
  979. 1 def smtp_envelope_from( val = nil )
  980. if val
  981. self.smtp_envelope_from = val
  982. else
  983. @smtp_envelope_from || return_path || sender || from_addrs.first
  984. end
  985. end
  986. # Sets the From address on the SMTP Envelope.
  987. #
  988. # Example:
  989. #
  990. # mail.smtp_envelope_from = 'Mikel <mikel@test.lindsaar.net>'
  991. # mail.smtp_envelope_from #=> 'mikel@test.lindsaar.net'
  992. 1 def smtp_envelope_from=( val )
  993. @smtp_envelope_from = val
  994. end
  995. # Returns the SMTP Envelope To value of the mail object.
  996. #
  997. # Defaults to #destinations: To, Cc, and Bcc addresses.
  998. #
  999. # Example:
  1000. #
  1001. # mail.smtp_envelope_to = 'Mikel <mikel@test.lindsaar.net>'
  1002. # mail.smtp_envelope_to #=> 'mikel@test.lindsaar.net'
  1003. #
  1004. # Also allows you to set the value by passing a value as a parameter
  1005. #
  1006. # Example:
  1007. #
  1008. # mail.smtp_envelope_to ['Mikel <mikel@test.lindsaar.net>', 'Lindsaar <lindsaar@test.lindsaar.net>']
  1009. # mail.smtp_envelope_to #=> ['mikel@test.lindsaar.net', 'lindsaar@test.lindsaar.net']
  1010. 1 def smtp_envelope_to( val = nil )
  1011. if val
  1012. self.smtp_envelope_to = val
  1013. else
  1014. @smtp_envelope_to || destinations
  1015. end
  1016. end
  1017. # Sets the To addresses on the SMTP Envelope.
  1018. #
  1019. # Example:
  1020. #
  1021. # mail.smtp_envelope_to = 'Mikel <mikel@test.lindsaar.net>'
  1022. # mail.smtp_envelope_to #=> 'mikel@test.lindsaar.net'
  1023. #
  1024. # mail.smtp_envelope_to = ['Mikel <mikel@test.lindsaar.net>', 'Lindsaar <lindsaar@test.lindsaar.net>']
  1025. # mail.smtp_envelope_to #=> ['mikel@test.lindsaar.net', 'lindsaar@test.lindsaar.net']
  1026. 1 def smtp_envelope_to=( val )
  1027. @smtp_envelope_to =
  1028. case val
  1029. when Array, NilClass
  1030. val
  1031. else
  1032. [val]
  1033. end
  1034. end
  1035. # Returns the decoded value of the subject field, as a single string.
  1036. #
  1037. # Example:
  1038. #
  1039. # mail.subject = "G'Day mate"
  1040. # mail.subject #=> "G'Day mate"
  1041. # mail.subject = '=?UTF-8?Q?This_is_=E3=81=82_string?='
  1042. # mail.subject #=> "This is ��� string"
  1043. #
  1044. # Also allows you to set the value by passing a value as a parameter
  1045. #
  1046. # Example:
  1047. #
  1048. # mail.subject "G'Day mate"
  1049. # mail.subject #=> "G'Day mate"
  1050. 1 def subject( val = nil )
  1051. default :subject, val
  1052. end
  1053. # Sets the Subject value of the mail object, pass in a string of the field
  1054. #
  1055. # Example:
  1056. #
  1057. # mail.subject = '=?UTF-8?Q?This_is_=E3=81=82_string?='
  1058. # mail.subject #=> "This is ��� string"
  1059. 1 def subject=( val )
  1060. header[:subject] = val
  1061. end
  1062. # Returns the To value of the mail object as an array of strings of
  1063. # address specs.
  1064. #
  1065. # Example:
  1066. #
  1067. # mail.to = 'Mikel <mikel@test.lindsaar.net>'
  1068. # mail.to #=> ['mikel@test.lindsaar.net']
  1069. # mail.to = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  1070. # mail.to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  1071. #
  1072. # Also allows you to set the value by passing a value as a parameter
  1073. #
  1074. # Example:
  1075. #
  1076. # mail.to 'Mikel <mikel@test.lindsaar.net>'
  1077. # mail.to #=> ['mikel@test.lindsaar.net']
  1078. #
  1079. # Additionally, you can append new addresses to the returned Array like
  1080. # object.
  1081. #
  1082. # Example:
  1083. #
  1084. # mail.to 'Mikel <mikel@test.lindsaar.net>'
  1085. # mail.to << 'ada@test.lindsaar.net'
  1086. # mail.to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  1087. 1 def to( val = nil )
  1088. default :to, val
  1089. end
  1090. # Sets the To value of the mail object, pass in a string of the field
  1091. #
  1092. # Example:
  1093. #
  1094. # mail.to = 'Mikel <mikel@test.lindsaar.net>'
  1095. # mail.to #=> ['mikel@test.lindsaar.net']
  1096. # mail.to = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
  1097. # mail.to #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
  1098. 1 def to=( val )
  1099. header[:to] = val
  1100. end
  1101. # Returns the default value of the field requested as a symbol.
  1102. #
  1103. # Each header field has a :default method which returns the most common use case for
  1104. # that field, for example, the date field types will return a DateTime object when
  1105. # sent :default, the subject, or unstructured fields will return a decoded string of
  1106. # their value, the address field types will return a single addr_spec or an array of
  1107. # addr_specs if there is more than one.
  1108. 1 def default( sym, val = nil )
  1109. if val
  1110. header[sym] = val
  1111. elsif field = header[sym]
  1112. field.default
  1113. end
  1114. end
  1115. # Sets the body object of the message object.
  1116. #
  1117. # Example:
  1118. #
  1119. # mail.body = 'This is the body'
  1120. # mail.body #=> #<Mail::Body:0x13919c @raw_source="This is the bo...
  1121. #
  1122. # You can also reset the body of an Message object by setting body to nil
  1123. #
  1124. # Example:
  1125. #
  1126. # mail.body = 'this is the body'
  1127. # mail.body.encoded #=> 'this is the body'
  1128. # mail.body = nil
  1129. # mail.body.encoded #=> ''
  1130. #
  1131. # If you try and set the body of an email that is a multipart email, then instead
  1132. # of deleting all the parts of your email, mail will add a text/plain part to
  1133. # your email:
  1134. #
  1135. # mail.add_file 'somefilename.png'
  1136. # mail.parts.length #=> 1
  1137. # mail.body = "This is a body"
  1138. # mail.parts.length #=> 2
  1139. # mail.parts.last.content_type.content_type #=> 'This is a body'
  1140. 1 def body=(value)
  1141. body_lazy(value)
  1142. end
  1143. # Returns the body of the message object. Or, if passed
  1144. # a parameter sets the value.
  1145. #
  1146. # Example:
  1147. #
  1148. # mail = Mail::Message.new('To: mikel\r\n\r\nThis is the body')
  1149. # mail.body #=> #<Mail::Body:0x13919c @raw_source="This is the bo...
  1150. #
  1151. # mail.body 'This is another body'
  1152. # mail.body #=> #<Mail::Body:0x13919c @raw_source="This is anothe...
  1153. 1 def body(value = nil)
  1154. if value
  1155. self.body = value
  1156. else
  1157. process_body_raw if @body_raw
  1158. @body
  1159. end
  1160. end
  1161. 1 def body_encoding(value = nil)
  1162. if value.nil?
  1163. body.encoding
  1164. else
  1165. body.encoding = value
  1166. end
  1167. end
  1168. 1 def body_encoding=(value)
  1169. body.encoding = value
  1170. end
  1171. # Returns the list of addresses this message should be sent to by
  1172. # collecting the addresses off the to, cc and bcc fields.
  1173. #
  1174. # Example:
  1175. #
  1176. # mail.to = 'mikel@test.lindsaar.net'
  1177. # mail.cc = 'sam@test.lindsaar.net'
  1178. # mail.bcc = 'bob@test.lindsaar.net'
  1179. # mail.destinations.length #=> 3
  1180. # mail.destinations.first #=> 'mikel@test.lindsaar.net'
  1181. 1 def destinations
  1182. [to_addrs, cc_addrs, bcc_addrs].compact.flatten
  1183. end
  1184. # Returns an array of addresses (the encoded value) in the From field,
  1185. # if no From field, returns an empty array
  1186. 1 def from_addrs
  1187. from ? [from].flatten : []
  1188. end
  1189. # Returns an array of addresses (the encoded value) in the To field,
  1190. # if no To field, returns an empty array
  1191. 1 def to_addrs
  1192. to ? [to].flatten : []
  1193. end
  1194. # Returns an array of addresses (the encoded value) in the Cc field,
  1195. # if no Cc field, returns an empty array
  1196. 1 def cc_addrs
  1197. cc ? [cc].flatten : []
  1198. end
  1199. # Returns an array of addresses (the encoded value) in the Bcc field,
  1200. # if no Bcc field, returns an empty array
  1201. 1 def bcc_addrs
  1202. bcc ? [bcc].flatten : []
  1203. end
  1204. # Allows you to add an arbitrary header
  1205. #
  1206. # Example:
  1207. #
  1208. # mail['foo'] = '1234'
  1209. # mail['foo'].to_s #=> '1234'
  1210. 1 def []=(name, value)
  1211. if name.to_s == 'body'
  1212. self.body = value
  1213. elsif name.to_s =~ /content[-_]type/i
  1214. header[name] = value
  1215. elsif name.to_s == 'charset'
  1216. self.charset = value
  1217. else
  1218. header[name] = value
  1219. end
  1220. end
  1221. # Allows you to read an arbitrary header
  1222. #
  1223. # Example:
  1224. #
  1225. # mail['foo'] = '1234'
  1226. # mail['foo'].to_s #=> '1234'
  1227. 1 def [](name)
  1228. header[underscoreize(name)]
  1229. end
  1230. # Method Missing in this implementation allows you to set any of the
  1231. # standard fields directly as you would the "to", "subject" etc.
  1232. #
  1233. # Those fields used most often (to, subject et al) are given their
  1234. # own method for ease of documentation and also to avoid the hook
  1235. # call to method missing.
  1236. #
  1237. # This will only catch the known fields listed in:
  1238. #
  1239. # Mail::Field::KNOWN_FIELDS
  1240. #
  1241. # as per RFC 2822, any ruby string or method name could pretty much
  1242. # be a field name, so we don't want to just catch ANYTHING sent to
  1243. # a message object and interpret it as a header.
  1244. #
  1245. # This method provides all three types of header call to set, read
  1246. # and explicitly set with the = operator
  1247. #
  1248. # Examples:
  1249. #
  1250. # mail.comments = 'These are some comments'
  1251. # mail.comments #=> 'These are some comments'
  1252. #
  1253. # mail.comments 'These are other comments'
  1254. # mail.comments #=> 'These are other comments'
  1255. #
  1256. #
  1257. # mail.date = 'Tue, 1 Jul 2003 10:52:37 +0200'
  1258. # mail.date.to_s #=> 'Tue, 1 Jul 2003 10:52:37 +0200'
  1259. #
  1260. # mail.date 'Tue, 1 Jul 2003 10:52:37 +0200'
  1261. # mail.date.to_s #=> 'Tue, 1 Jul 2003 10:52:37 +0200'
  1262. #
  1263. #
  1264. # mail.resent_msg_id = '<1234@resent_msg_id.lindsaar.net>'
  1265. # mail.resent_msg_id #=> '<1234@resent_msg_id.lindsaar.net>'
  1266. #
  1267. # mail.resent_msg_id '<4567@resent_msg_id.lindsaar.net>'
  1268. # mail.resent_msg_id #=> '<4567@resent_msg_id.lindsaar.net>'
  1269. 1 def method_missing(name, *args, &block)
  1270. #:nodoc:
  1271. # Only take the structured fields, as we could take _anything_ really
  1272. # as it could become an optional field... "but therin lies the dark side"
  1273. field_name = underscoreize(name).chomp("=")
  1274. if Mail::Field::KNOWN_FIELDS.include?(field_name)
  1275. if args.empty?
  1276. header[field_name]
  1277. else
  1278. header[field_name] = args.first
  1279. end
  1280. else
  1281. super # otherwise pass it on
  1282. end
  1283. #:startdoc:
  1284. end
  1285. # Returns an FieldList of all the fields in the header in the order that
  1286. # they appear in the header
  1287. 1 def header_fields
  1288. header.fields
  1289. end
  1290. # Returns true if the message has a message ID field, the field may or may
  1291. # not have a value, but the field exists or not.
  1292. 1 def has_message_id?
  1293. header.has_message_id?
  1294. end
  1295. # Returns true if the message has a Date field, the field may or may
  1296. # not have a value, but the field exists or not.
  1297. 1 def has_date?
  1298. header.has_date?
  1299. end
  1300. # Returns true if the message has a Mime-Version field, the field may or may
  1301. # not have a value, but the field exists or not.
  1302. 1 def has_mime_version?
  1303. header.has_mime_version?
  1304. end
  1305. 1 def has_content_type?
  1306. tmp = header[:content_type].main_type rescue nil
  1307. !!tmp
  1308. end
  1309. 1 def has_charset?
  1310. tmp = header[:content_type].parameters rescue nil
  1311. !!(has_content_type? && tmp && tmp['charset'])
  1312. end
  1313. 1 def has_content_transfer_encoding?
  1314. header[:content_transfer_encoding] && Utilities.blank?(header[:content_transfer_encoding].errors)
  1315. end
  1316. 1 def has_transfer_encoding? # :nodoc:
  1317. warn(":has_transfer_encoding? is deprecated in Mail 1.4.3. Please use has_content_transfer_encoding?\n#{caller}")
  1318. has_content_transfer_encoding?
  1319. end
  1320. # Creates a new empty Message-ID field and inserts it in the correct order
  1321. # into the Header. The MessageIdField object will automatically generate
  1322. # a unique message ID if you try and encode it or output it to_s without
  1323. # specifying a message id.
  1324. #
  1325. # It will preserve the message ID you specify if you do.
  1326. 1 def add_message_id(msg_id_val = '')
  1327. header['message-id'] = msg_id_val
  1328. end
  1329. # Creates a new empty Date field and inserts it in the correct order
  1330. # into the Header. The DateField object will automatically generate
  1331. # DateTime.now's date if you try and encode it or output it to_s without
  1332. # specifying a date yourself.
  1333. #
  1334. # It will preserve any date you specify if you do.
  1335. 1 def add_date(date_val = '')
  1336. header['date'] = date_val
  1337. end
  1338. # Creates a new empty Mime Version field and inserts it in the correct order
  1339. # into the Header. The MimeVersion object will automatically generate
  1340. # set itself to '1.0' if you try and encode it or output it to_s without
  1341. # specifying a version yourself.
  1342. #
  1343. # It will preserve any date you specify if you do.
  1344. 1 def add_mime_version(ver_val = '')
  1345. header['mime-version'] = ver_val
  1346. end
  1347. # Adds a content type and charset if the body is US-ASCII
  1348. #
  1349. # Otherwise raises a warning
  1350. 1 def add_content_type
  1351. header[:content_type] = 'text/plain'
  1352. end
  1353. # Adds a content type and charset if the body is US-ASCII
  1354. #
  1355. # Otherwise raises a warning
  1356. 1 def add_charset
  1357. if !body.empty?
  1358. # Only give a warning if this isn't an attachment, has non US-ASCII and the user
  1359. # has not specified an encoding explicitly.
  1360. if @defaulted_charset && !body.raw_source.ascii_only? && !self.attachment?
  1361. warning = "Non US-ASCII detected and no charset defined.\nDefaulting to UTF-8, set your own if this is incorrect.\n"
  1362. warn(warning)
  1363. end
  1364. header[:content_type].parameters['charset'] = @charset
  1365. end
  1366. end
  1367. # Adds a content transfer encoding
  1368. 1 def add_content_transfer_encoding
  1369. header[:content_transfer_encoding] ||= body.default_encoding
  1370. end
  1371. 1 def add_transfer_encoding # :nodoc:
  1372. warn(":add_transfer_encoding is deprecated in Mail 1.4.3. Please use add_content_transfer_encoding\n#{caller}")
  1373. add_content_transfer_encoding
  1374. end
  1375. 1 def transfer_encoding # :nodoc:
  1376. warn(":transfer_encoding is deprecated in Mail 1.4.3. Please use content_transfer_encoding\n#{caller}")
  1377. content_transfer_encoding
  1378. end
  1379. # Returns the MIME media type of part we are on, this is taken from the content-type header
  1380. 1 def mime_type
  1381. has_content_type? ? header[:content_type].string : nil rescue nil
  1382. end
  1383. 1 def message_content_type
  1384. warn(":message_content_type is deprecated in Mail 1.4.3. Please use mime_type\n#{caller}")
  1385. mime_type
  1386. end
  1387. # Returns the character set defined in the content type field
  1388. 1 def charset
  1389. if @header
  1390. has_content_type? ? content_type_parameters['charset'] : @charset
  1391. else
  1392. @charset
  1393. end
  1394. end
  1395. # Sets the charset to the supplied value.
  1396. 1 def charset=(value)
  1397. @defaulted_charset = false
  1398. @charset = value
  1399. @header.charset = value
  1400. end
  1401. # Returns the main content type
  1402. 1 def main_type
  1403. has_content_type? ? header[:content_type].main_type : nil rescue nil
  1404. end
  1405. # Returns the sub content type
  1406. 1 def sub_type
  1407. has_content_type? ? header[:content_type].sub_type : nil rescue nil
  1408. end
  1409. # Returns the content type parameters
  1410. 1 def mime_parameters
  1411. warn(':mime_parameters is deprecated in Mail 1.4.3, please use :content_type_parameters instead')
  1412. content_type_parameters
  1413. end
  1414. # Returns the content type parameters
  1415. 1 def content_type_parameters
  1416. has_content_type? ? header[:content_type].parameters : nil rescue nil
  1417. end
  1418. # Returns true if the message is multipart
  1419. 1 def multipart?
  1420. has_content_type? ? !!(main_type =~ /^multipart$/i) : false
  1421. end
  1422. # Returns true if the message is a multipart/report
  1423. 1 def multipart_report?
  1424. multipart? && sub_type =~ /^report$/i
  1425. end
  1426. # Returns true if the message is a multipart/report; report-type=delivery-status;
  1427. 1 def delivery_status_report?
  1428. multipart_report? && content_type_parameters['report-type'] =~ /^delivery-status$/i
  1429. end
  1430. # returns the part in a multipart/report email that has the content-type delivery-status
  1431. 1 def delivery_status_part
  1432. unless defined? @delivery_status_part
  1433. @delivery_status_part =
  1434. if delivery_status_report?
  1435. parts.detect(&:delivery_status_report_part?)
  1436. end
  1437. end
  1438. @delivery_status_part
  1439. end
  1440. 1 def bounced?
  1441. delivery_status_part and delivery_status_part.bounced?
  1442. end
  1443. 1 def action
  1444. delivery_status_part and delivery_status_part.action
  1445. end
  1446. 1 def final_recipient
  1447. delivery_status_part and delivery_status_part.final_recipient
  1448. end
  1449. 1 def error_status
  1450. delivery_status_part and delivery_status_part.error_status
  1451. end
  1452. 1 def diagnostic_code
  1453. delivery_status_part and delivery_status_part.diagnostic_code
  1454. end
  1455. 1 def remote_mta
  1456. delivery_status_part and delivery_status_part.remote_mta
  1457. end
  1458. 1 def retryable?
  1459. delivery_status_part and delivery_status_part.retryable?
  1460. end
  1461. # Returns the current boundary for this message part
  1462. 1 def boundary
  1463. content_type_parameters ? content_type_parameters['boundary'] : nil
  1464. end
  1465. # Returns a parts list object of all the parts in the message
  1466. 1 def parts
  1467. body.parts
  1468. end
  1469. # Returns an AttachmentsList object, which holds all of the attachments in
  1470. # the receiver object (either the entire email or a part within) and all
  1471. # of its descendants.
  1472. #
  1473. # It also allows you to add attachments to the mail object directly, like so:
  1474. #
  1475. # mail.attachments['filename.jpg'] = File.read('/path/to/filename.jpg')
  1476. #
  1477. # If you do this, then Mail will take the file name and work out the MIME media type
  1478. # set the Content-Type, Content-Disposition, Content-Transfer-Encoding and
  1479. # base64 encode the contents of the attachment all for you.
  1480. #
  1481. # You can also specify overrides if you want by passing a hash instead of a string:
  1482. #
  1483. # mail.attachments['filename.jpg'] = {:mime_type => 'application/x-gzip',
  1484. # :content => File.read('/path/to/filename.jpg')}
  1485. #
  1486. # If you want to use a different encoding than Base64, you can pass an encoding in,
  1487. # but then it is up to you to pass in the content pre-encoded, and don't expect
  1488. # Mail to know how to decode this data:
  1489. #
  1490. # file_content = SpecialEncode(File.read('/path/to/filename.jpg'))
  1491. # mail.attachments['filename.jpg'] = {:mime_type => 'application/x-gzip',
  1492. # :encoding => 'SpecialEncoding',
  1493. # :content => file_content }
  1494. #
  1495. # You can also search for specific attachments:
  1496. #
  1497. # # By Filename
  1498. # mail.attachments['filename.jpg'] #=> Mail::Part object or nil
  1499. #
  1500. # # or by index
  1501. # mail.attachments[0] #=> Mail::Part (first attachment)
  1502. #
  1503. 1 def attachments
  1504. parts.attachments
  1505. end
  1506. 1 def has_attachments?
  1507. !attachments.empty?
  1508. end
  1509. # Accessor for html_part
  1510. 1 def html_part(&block)
  1511. if block_given?
  1512. self.html_part = Mail::Part.new(:content_type => 'text/html', &block)
  1513. else
  1514. @html_part || find_first_mime_type('text/html')
  1515. end
  1516. end
  1517. # Accessor for text_part
  1518. 1 def text_part(&block)
  1519. if block_given?
  1520. self.text_part = Mail::Part.new(:content_type => 'text/plain', &block)
  1521. else
  1522. @text_part || find_first_mime_type('text/plain')
  1523. end
  1524. end
  1525. # Helper to add a html part to a multipart/alternative email. If this and
  1526. # text_part are both defined in a message, then it will be a multipart/alternative
  1527. # message and set itself that way.
  1528. 1 def html_part=(msg)
  1529. # Assign the html part and set multipart/alternative if there's a text part.
  1530. if msg
  1531. msg = Mail::Part.new(:body => msg) unless msg.kind_of?(Mail::Message)
  1532. @html_part = msg
  1533. @html_part.content_type = 'text/html' unless @html_part.has_content_type?
  1534. add_multipart_alternate_header if text_part
  1535. add_part @html_part
  1536. # If nil, delete the html part and back out of multipart/alternative.
  1537. elsif @html_part
  1538. parts.delete_if { |p| p.object_id == @html_part.object_id }
  1539. @html_part = nil
  1540. if text_part
  1541. self.content_type = nil
  1542. body.boundary = nil
  1543. end
  1544. end
  1545. end
  1546. # Helper to add a text part to a multipart/alternative email. If this and
  1547. # html_part are both defined in a message, then it will be a multipart/alternative
  1548. # message and set itself that way.
  1549. 1 def text_part=(msg)
  1550. # Assign the text part and set multipart/alternative if there's an html part.
  1551. if msg
  1552. msg = Mail::Part.new(:body => msg) unless msg.kind_of?(Mail::Message)
  1553. @text_part = msg
  1554. @text_part.content_type = 'text/plain' unless @text_part.has_content_type?
  1555. add_multipart_alternate_header if html_part
  1556. add_part @text_part
  1557. # If nil, delete the text part and back out of multipart/alternative.
  1558. elsif @text_part
  1559. parts.delete_if { |p| p.object_id == @text_part.object_id }
  1560. @text_part = nil
  1561. if html_part
  1562. self.content_type = nil
  1563. body.boundary = nil
  1564. end
  1565. end
  1566. end
  1567. # Adds a part to the parts list or creates the part list
  1568. 1 def add_part(part)
  1569. if !body.multipart? && !Utilities.blank?(self.body.decoded)
  1570. @text_part = Mail::Part.new('Content-Type: text/plain;')
  1571. @text_part.body = body.decoded
  1572. self.body << @text_part
  1573. add_multipart_alternate_header
  1574. end
  1575. add_boundary
  1576. self.body << part
  1577. end
  1578. # Allows you to add a part in block form to an existing mail message object
  1579. #
  1580. # Example:
  1581. #
  1582. # mail = Mail.new do
  1583. # part :content_type => "multipart/alternative", :content_disposition => "inline" do |p|
  1584. # p.part :content_type => "text/plain", :body => "test text\nline #2"
  1585. # p.part :content_type => "text/html", :body => "<b>test</b> HTML<br/>\nline #2"
  1586. # end
  1587. # end
  1588. 1 def part(params = {})
  1589. new_part = Part.new(params)
  1590. yield new_part if block_given?
  1591. add_part(new_part)
  1592. end
  1593. # Adds a file to the message. You have two options with this method, you can
  1594. # just pass in the absolute path to the file you want and Mail will read the file,
  1595. # get the filename from the path you pass in and guess the MIME media type, or you
  1596. # can pass in the filename as a string, and pass in the file content as a blob.
  1597. #
  1598. # Example:
  1599. #
  1600. # m = Mail.new
  1601. # m.add_file('/path/to/filename.png')
  1602. #
  1603. # m = Mail.new
  1604. # m.add_file(:filename => 'filename.png', :content => File.read('/path/to/file.jpg'))
  1605. #
  1606. # Note also that if you add a file to an existing message, Mail will convert that message
  1607. # to a MIME multipart email, moving whatever plain text body you had into its own text
  1608. # plain part.
  1609. #
  1610. # Example:
  1611. #
  1612. # m = Mail.new do
  1613. # body 'this is some text'
  1614. # end
  1615. # m.multipart? #=> false
  1616. # m.add_file('/path/to/filename.png')
  1617. # m.multipart? #=> true
  1618. # m.parts.first.content_type.content_type #=> 'text/plain'
  1619. # m.parts.last.content_type.content_type #=> 'image/png'
  1620. #
  1621. # See also #attachments
  1622. 1 def add_file(values)
  1623. convert_to_multipart unless self.multipart? || Utilities.blank?(self.body.decoded)
  1624. add_multipart_mixed_header
  1625. if values.is_a?(String)
  1626. basename = File.basename(values)
  1627. filedata = File.open(values, 'rb') { |f| f.read }
  1628. else
  1629. basename = values[:filename]
  1630. filedata = values
  1631. end
  1632. self.attachments[basename] = filedata
  1633. end
  1634. 1 def convert_to_multipart
  1635. text = body.decoded
  1636. self.body = ''
  1637. text_part = Mail::Part.new({:content_type => 'text/plain;',
  1638. :body => text})
  1639. text_part.charset = charset unless @defaulted_charset
  1640. self.body << text_part
  1641. end
  1642. # Encodes the message, calls encode on all its parts, gets an email message
  1643. # ready to send
  1644. 1 def ready_to_send!
  1645. identify_and_set_transfer_encoding
  1646. parts.each do |part|
  1647. part.transport_encoding = transport_encoding
  1648. part.ready_to_send!
  1649. end
  1650. add_required_fields
  1651. end
  1652. 1 def encode!
  1653. warn("Deprecated in 1.1.0 in favour of :ready_to_send! as it is less confusing with encoding and decoding.")
  1654. ready_to_send!
  1655. end
  1656. # Outputs an encoded string representation of the mail message including
  1657. # all headers, attachments, etc. This is an encoded email in US-ASCII,
  1658. # so it is able to be directly sent to an email server.
  1659. 1 def encoded
  1660. ready_to_send!
  1661. buffer = header.encoded
  1662. buffer << "\r\n"
  1663. buffer << body.encoded(content_transfer_encoding)
  1664. buffer
  1665. end
  1666. 1 def without_attachments!
  1667. if has_attachments?
  1668. parts.delete_if { |p| p.attachment? }
  1669. reencoded = parts.empty? ? '' : body.encoded(content_transfer_encoding)
  1670. @body = nil # So the new parts won't be added to the existing body
  1671. self.body = reencoded
  1672. end
  1673. self
  1674. end
  1675. 1 def to_yaml(opts = {})
  1676. hash = {}
  1677. hash['headers'] = {}
  1678. header.fields.each do |field|
  1679. hash['headers'][field.name] = field.value
  1680. end
  1681. hash['delivery_handler'] = delivery_handler.to_s if delivery_handler
  1682. hash['transport_encoding'] = transport_encoding.to_s
  1683. special_variables = [:@header, :@delivery_handler, :@transport_encoding]
  1684. if multipart?
  1685. hash['multipart_body'] = []
  1686. body.parts.map { |part| hash['multipart_body'] << part.to_yaml }
  1687. special_variables.push(:@body, :@text_part, :@html_part)
  1688. end
  1689. (instance_variables.map(&:to_sym) - special_variables).each do |var|
  1690. hash[var.to_s] = instance_variable_get(var)
  1691. end
  1692. hash.to_yaml(opts)
  1693. end
  1694. 1 def self.from_yaml(str)
  1695. hash = YAML.load(str)
  1696. m = self.new(:headers => hash['headers'])
  1697. hash.delete('headers')
  1698. hash.each do |k,v|
  1699. case
  1700. when k == 'delivery_handler'
  1701. begin
  1702. m.delivery_handler = Object.const_get(v) unless Utilities.blank?(v)
  1703. rescue NameError
  1704. end
  1705. when k == 'transport_encoding'
  1706. m.transport_encoding(v)
  1707. when k == 'multipart_body'
  1708. v.map {|part| m.add_part Mail::Part.from_yaml(part) }
  1709. when k =~ /^@/
  1710. m.instance_variable_set(k.to_sym, v)
  1711. end
  1712. end
  1713. m
  1714. end
  1715. 1 def self.from_hash(hash)
  1716. Mail::Message.new(hash)
  1717. end
  1718. 1 def to_s
  1719. encoded
  1720. end
  1721. 1 def inspect
  1722. "#<#{self.class}:#{self.object_id}, Multipart: #{multipart?}, Headers: #{header.field_summary}>"
  1723. end
  1724. 1 def decoded
  1725. case
  1726. when self.text?
  1727. decode_body_as_text
  1728. when self.attachment?
  1729. decode_body
  1730. when !self.multipart?
  1731. body.decoded
  1732. else
  1733. raise NoMethodError, 'Can not decode an entire message, try calling #decoded on the various fields and body or parts if it is a multipart message.'
  1734. end
  1735. end
  1736. 1 def read
  1737. if self.attachment?
  1738. decode_body
  1739. else
  1740. raise NoMethodError, 'Can not call read on a part unless it is an attachment.'
  1741. end
  1742. end
  1743. 1 def decode_body
  1744. body.decoded
  1745. end
  1746. # Returns true if this part is an attachment,
  1747. # false otherwise.
  1748. 1 def attachment?
  1749. !!find_attachment
  1750. end
  1751. # Returns the attachment data if there is any
  1752. 1 def attachment
  1753. @attachment
  1754. end
  1755. # Returns the filename of the attachment
  1756. 1 def filename
  1757. find_attachment
  1758. end
  1759. 1 def all_parts
  1760. parts.map { |p| [p, p.all_parts] }.flatten
  1761. end
  1762. 1 def find_first_mime_type(mt)
  1763. all_parts.detect { |p| p.mime_type == mt && !p.attachment? }
  1764. end
  1765. # Skips the deletion of this message. All other messages
  1766. # flagged for delete still will be deleted at session close (i.e. when
  1767. # #find exits). Only has an effect if you're using #find_and_delete
  1768. # or #find with :delete_after_find set to true.
  1769. 1 def skip_deletion
  1770. @mark_for_delete = false
  1771. end
  1772. # Sets whether this message should be deleted at session close (i.e.
  1773. # after #find). Message will only be deleted if messages are retrieved
  1774. # using the #find_and_delete method, or by calling #find with
  1775. # :delete_after_find set to true.
  1776. 1 def mark_for_delete=(value = true)
  1777. @mark_for_delete = value
  1778. end
  1779. # Returns whether message will be marked for deletion.
  1780. # If so, the message will be deleted at session close (i.e. after #find
  1781. # exits), but only if also using the #find_and_delete method, or by
  1782. # calling #find with :delete_after_find set to true.
  1783. #
  1784. # Side-note: Just to be clear, this method will return true even if
  1785. # the message hasn't yet been marked for delete on the mail server.
  1786. # However, if this method returns true, it *will be* marked on the
  1787. # server after each block yields back to #find or #find_and_delete.
  1788. 1 def is_marked_for_delete?
  1789. return @mark_for_delete
  1790. end
  1791. 1 def text?
  1792. has_content_type? ? !!(main_type =~ /^text$/i) : false
  1793. end
  1794. 1 private
  1795. 1 HEADER_SEPARATOR = /#{Constants::CRLF}#{Constants::CRLF}/
  1796. # 2.1. General Description
  1797. # A message consists of header fields (collectively called "the header
  1798. # of the message") followed, optionally, by a body. The header is a
  1799. # sequence of lines of characters with special syntax as defined in
  1800. # this standard. The body is simply a sequence of characters that
  1801. # follows the header and is separated from the header by an empty line
  1802. # (i.e., a line with nothing preceding the CRLF).
  1803. 1 def parse_message
  1804. header_part, body_part = raw_source.lstrip.split(HEADER_SEPARATOR, 2)
  1805. self.header = header_part
  1806. self.body = body_part
  1807. end
  1808. 1 def raw_source=(value)
  1809. @raw_source = value
  1810. end
  1811. # see comments to body=. We take data and process it lazily
  1812. 1 def body_lazy(value)
  1813. process_body_raw if @body_raw && value
  1814. case
  1815. when value == nil || value.length<=0
  1816. @body = Mail::Body.new('')
  1817. @body_raw = nil
  1818. add_encoding_to_body
  1819. when @body && @body.multipart?
  1820. self.text_part = value
  1821. else
  1822. @body_raw = value
  1823. end
  1824. end
  1825. 1 def process_body_raw
  1826. @body = Mail::Body.new(@body_raw)
  1827. @body_raw = nil
  1828. separate_parts if @separate_parts
  1829. add_encoding_to_body
  1830. end
  1831. 1 def set_envelope_header
  1832. raw_string = raw_source.to_s
  1833. if match_data = raw_string.match(/\AFrom\s(#{TEXT}+)#{Constants::CRLF}/m)
  1834. set_envelope(match_data[1])
  1835. self.raw_source = raw_string.sub(match_data[0], "")
  1836. end
  1837. end
  1838. 1 def separate_parts
  1839. body.split!(boundary)
  1840. end
  1841. 1 def allowed_encodings
  1842. case mime_type
  1843. when 'message/rfc822'
  1844. [Encodings::SevenBit, Encodings::EightBit, Encodings::Binary]
  1845. end
  1846. end
  1847. 1 def add_encoding_to_body
  1848. if has_content_transfer_encoding?
  1849. @body.encoding = content_transfer_encoding
  1850. end
  1851. end
  1852. 1 def identify_and_set_transfer_encoding
  1853. if body && body.multipart?
  1854. self.content_transfer_encoding = @transport_encoding
  1855. else
  1856. self.content_transfer_encoding = body.negotiate_best_encoding(@transport_encoding, allowed_encodings).to_s
  1857. end
  1858. end
  1859. 1 def add_required_fields
  1860. add_required_message_fields
  1861. add_multipart_mixed_header if body.multipart?
  1862. add_content_type unless has_content_type?
  1863. add_charset if text? && !has_charset?
  1864. add_content_transfer_encoding unless has_content_transfer_encoding?
  1865. end
  1866. 1 def add_required_message_fields
  1867. add_date unless has_date?
  1868. add_mime_version unless has_mime_version?
  1869. add_message_id unless has_message_id?
  1870. end
  1871. 1 def add_multipart_alternate_header
  1872. header['content-type'] = ContentTypeField.with_boundary('multipart/alternative').value
  1873. header['content_type'].parameters[:charset] = @charset
  1874. body.boundary = boundary
  1875. end
  1876. 1 def add_boundary
  1877. unless body.boundary && boundary
  1878. header['content-type'] = 'multipart/mixed' unless header['content-type']
  1879. header['content-type'].parameters[:boundary] = ContentTypeField.generate_boundary
  1880. header['content_type'].parameters[:charset] = @charset
  1881. body.boundary = boundary
  1882. end
  1883. end
  1884. 1 def add_multipart_mixed_header
  1885. unless header['content-type']
  1886. header['content-type'] = ContentTypeField.with_boundary('multipart/mixed').value
  1887. header['content_type'].parameters[:charset] = @charset
  1888. body.boundary = boundary
  1889. end
  1890. end
  1891. 1 def init_with_hash(hash)
  1892. passed_in_options = IndifferentHash.new(hash)
  1893. self.raw_source = ''
  1894. @header = Mail::Header.new
  1895. @body = Mail::Body.new
  1896. @body_raw = nil
  1897. # We need to store the body until last, as we need all headers added first
  1898. body_content = nil
  1899. passed_in_options.each_pair do |k,v|
  1900. k = underscoreize(k).to_sym if k.class == String
  1901. if k == :headers
  1902. self.headers(v)
  1903. elsif k == :body
  1904. body_content = v
  1905. else
  1906. self[k] = v
  1907. end
  1908. end
  1909. if body_content
  1910. self.body = body_content
  1911. if has_content_transfer_encoding?
  1912. body.encoding = content_transfer_encoding
  1913. end
  1914. end
  1915. end
  1916. 1 def init_with_string(string)
  1917. self.raw_source = string
  1918. set_envelope_header
  1919. parse_message
  1920. @separate_parts = multipart?
  1921. end
  1922. # Returns the filename of the attachment (if it exists) or returns nil
  1923. 1 def find_attachment
  1924. content_type_name = header[:content_type].filename rescue nil
  1925. content_disp_name = header[:content_disposition].filename rescue nil
  1926. content_loc_name = header[:content_location].location rescue nil
  1927. case
  1928. when content_disposition && content_disp_name
  1929. filename = content_disp_name
  1930. when content_type && content_type_name
  1931. filename = content_type_name
  1932. when content_location && content_loc_name
  1933. filename = content_loc_name
  1934. else
  1935. filename = nil
  1936. end
  1937. filename = Mail::Encodings.decode_encode(filename, :decode) if filename rescue filename
  1938. filename
  1939. end
  1940. 1 def do_delivery
  1941. begin
  1942. if perform_deliveries
  1943. delivery_method.deliver!(self)
  1944. end
  1945. rescue => e # Net::SMTP errors or sendmail pipe errors
  1946. raise e if raise_delivery_errors
  1947. end
  1948. end
  1949. 1 def decode_body_as_text
  1950. Encodings.transcode_charset decode_body, charset, 'UTF-8'
  1951. end
  1952. end
  1953. end

target/rubygems/gems/mail-2.7.1/lib/mail/multibyte.rb

61.11% lines covered

18 relevant lines. 11 lines covered and 7 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. 1 require 'mail/multibyte/chars'
  4. 1 module Mail #:nodoc:
  5. 1 module Multibyte
  6. # Raised when a problem with the encoding was found.
  7. 1 class EncodingError < StandardError; end
  8. 1 class << self
  9. # The proxy class returned when calling mb_chars. You can use this accessor to configure your own proxy
  10. # class so you can support other encodings. See the Mail::Multibyte::Chars implementation for
  11. # an example how to do this.
  12. #
  13. # Example:
  14. # Mail::Multibyte.proxy_class = CharsForUTF32
  15. 1 attr_accessor :proxy_class
  16. end
  17. 1 self.proxy_class = Mail::Multibyte::Chars
  18. 1 if RUBY_VERSION >= "1.9"
  19. # == Multibyte proxy
  20. #
  21. # +mb_chars+ is a multibyte safe proxy for string methods.
  22. #
  23. # In Ruby 1.8 and older it creates and returns an instance of the Mail::Multibyte::Chars class which
  24. # encapsulates the original string. A Unicode safe version of all the String methods are defined on this proxy
  25. # class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsuled string.
  26. #
  27. # name = 'Claus M��ller'
  28. # name.reverse # => "rell??M sualC"
  29. # name.length # => 13
  30. #
  31. # name.mb_chars.reverse.to_s # => "rell��M sualC"
  32. # name.mb_chars.length # => 12
  33. #
  34. # In Ruby 1.9 and newer +mb_chars+ returns +self+ because String is (mostly) encoding aware. This means that
  35. # it becomes easy to run one version of your code on multiple Ruby versions.
  36. #
  37. # == Method chaining
  38. #
  39. # All the methods on the Chars proxy which normally return a string will return a Chars object. This allows
  40. # method chaining on the result of any of these methods.
  41. #
  42. # name.mb_chars.reverse.length # => 12
  43. #
  44. # == Interoperability and configuration
  45. #
  46. # The Chars object tries to be as interchangeable with String objects as possible: sorting and comparing between
  47. # String and Char work like expected. The bang! methods change the internal string representation in the Chars
  48. # object. Interoperability problems can be resolved easily with a +to_s+ call.
  49. #
  50. # For more information about the methods defined on the Chars proxy see Mail::Multibyte::Chars. For
  51. # information about how to change the default Multibyte behaviour see Mail::Multibyte.
  52. 1 def self.mb_chars(str)
  53. if proxy_class.consumes?(str)
  54. proxy_class.new(str)
  55. else
  56. str
  57. end
  58. end
  59. else
  60. def self.mb_chars(str)
  61. if proxy_class.wants?(str)
  62. proxy_class.new(str)
  63. else
  64. str
  65. end
  66. end
  67. end
  68. # Regular expressions that describe valid byte sequences for a character
  69. 1 VALID_CHARACTER = {
  70. # Borrowed from the Kconv library by Shinji KONO - (also as seen on the W3C site)
  71. 'UTF-8' => /\A(?:
  72. [\x00-\x7f] |
  73. [\xc2-\xdf] [\x80-\xbf] |
  74. \xe0 [\xa0-\xbf] [\x80-\xbf] |
  75. [\xe1-\xef] [\x80-\xbf] [\x80-\xbf] |
  76. \xf0 [\x90-\xbf] [\x80-\xbf] [\x80-\xbf] |
  77. [\xf1-\xf3] [\x80-\xbf] [\x80-\xbf] [\x80-\xbf] |
  78. \xf4 [\x80-\x8f] [\x80-\xbf] [\x80-\xbf])\z /xn,
  79. # Quick check for valid Shift-JIS characters, disregards the odd-even pairing
  80. 'Shift_JIS' => /\A(?:
  81. [\x00-\x7e\xa1-\xdf] |
  82. [\x81-\x9f\xe0-\xef] [\x40-\x7e\x80-\x9e\x9f-\xfc])\z /xn
  83. }
  84. end
  85. end
  86. 1 require 'mail/multibyte/utils'

target/rubygems/gems/mail-2.7.1/lib/mail/multibyte/chars.rb

24.85% lines covered

165 relevant lines. 41 lines covered and 124 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. 1 require 'mail/multibyte/unicode'
  4. 1 module Mail #:nodoc:
  5. 1 module Multibyte #:nodoc:
  6. # Chars enables you to work transparently with UTF-8 encoding in the Ruby String class without having extensive
  7. # knowledge about the encoding. A Chars object accepts a string upon initialization and proxies String methods in an
  8. # encoding safe manner. All the normal String methods are also implemented on the proxy.
  9. #
  10. # String methods are proxied through the Chars object, and can be accessed through the +mb_chars+ method. Methods
  11. # which would normally return a String object now return a Chars object so methods can be chained.
  12. #
  13. # "The Perfect String ".mb_chars.downcase.strip.normalize # => "the perfect string"
  14. #
  15. # Chars objects are perfectly interchangeable with String objects as long as no explicit class checks are made.
  16. # If certain methods do explicitly check the class, call +to_s+ before you pass chars objects to them.
  17. #
  18. # bad.explicit_checking_method "T".mb_chars.downcase.to_s
  19. #
  20. # The default Chars implementation assumes that the encoding of the string is UTF-8, if you want to handle different
  21. # encodings you can write your own multibyte string handler and configure it through
  22. # Mail::Multibyte.proxy_class.
  23. #
  24. # class CharsForUTF32
  25. # def size
  26. # @wrapped_string.size / 4
  27. # end
  28. #
  29. # def self.accepts?(string)
  30. # string.length % 4 == 0
  31. # end
  32. # end
  33. #
  34. # Mail::Multibyte.proxy_class = CharsForUTF32
  35. 1 class Chars
  36. 1 attr_reader :wrapped_string
  37. 1 alias to_s wrapped_string
  38. 1 alias to_str wrapped_string
  39. 1 if RUBY_VERSION >= "1.9"
  40. # Creates a new Chars instance by wrapping _string_.
  41. 1 def initialize(string)
  42. @wrapped_string = string.dup
  43. @wrapped_string.force_encoding(Encoding::UTF_8) unless @wrapped_string.frozen?
  44. end
  45. else
  46. def initialize(string) #:nodoc:
  47. @wrapped_string = string
  48. end
  49. end
  50. # Forward all undefined methods to the wrapped string.
  51. 1 def method_missing(method, *args, &block)
  52. if method.to_s =~ /!$/
  53. @wrapped_string.__send__(method, *args, &block)
  54. self
  55. else
  56. result = @wrapped_string.__send__(method, *args, &block)
  57. result.kind_of?(String) ? chars(result) : result
  58. end
  59. end
  60. # Returns +true+ if _obj_ responds to the given method. Private methods are included in the search
  61. # only if the optional second parameter evaluates to +true+.
  62. 1 def respond_to?(method, include_private=false)
  63. super || @wrapped_string.respond_to?(method, include_private) || false
  64. end
  65. # Enable more predictable duck-typing on String-like classes. See Object#acts_like?.
  66. 1 def acts_like_string?
  67. true
  68. end
  69. # Returns +true+ when the proxy class can handle the string. Returns +false+ otherwise.
  70. 1 def self.consumes?(string)
  71. # Unpack is a little bit faster than regular expressions.
  72. string.unpack('U*')
  73. true
  74. rescue ArgumentError
  75. false
  76. end
  77. 1 include Comparable
  78. # Returns -1, 0, or 1, depending on whether the Chars object is to be sorted before,
  79. # equal or after the object on the right side of the operation. It accepts any object
  80. # that implements +to_s+:
  81. #
  82. # '��'.mb_chars <=> '��'.mb_chars # => -1
  83. #
  84. # See <tt>String#<=></tt> for more details.
  85. 1 def <=>(other)
  86. @wrapped_string <=> other.to_s
  87. end
  88. 1 if RUBY_VERSION < "1.9"
  89. # Returns +true+ if the Chars class can and should act as a proxy for the string _string_. Returns
  90. # +false+ otherwise.
  91. def self.wants?(string)
  92. $KCODE == 'UTF8' && consumes?(string)
  93. end
  94. # Returns a new Chars object containing the _other_ object concatenated to the string.
  95. #
  96. # Example:
  97. # (Mail::Multibyte.mb_chars('Caf��') + ' p��rifer��l').to_s # => "Caf�� p��rifer��l"
  98. def +(other)
  99. chars(@wrapped_string + other)
  100. end
  101. # Like <tt>String#=~</tt> only it returns the character offset (in codepoints) instead of the byte offset.
  102. #
  103. # Example:
  104. # Mail::Multibyte.mb_chars('Caf�� p��rifer��l') =~ /��/ # => 12
  105. def =~(other)
  106. translate_offset(@wrapped_string =~ other)
  107. end
  108. # Inserts the passed string at specified codepoint offsets.
  109. #
  110. # Example:
  111. # Mail::Multibyte.mb_chars('Caf��').insert(4, ' p��rifer��l').to_s # => "Caf�� p��rifer��l"
  112. def insert(offset, fragment)
  113. unpacked = Unicode.u_unpack(@wrapped_string)
  114. unless offset > unpacked.length
  115. @wrapped_string.replace(
  116. Unicode.u_unpack(@wrapped_string).insert(offset, *Unicode.u_unpack(fragment)).pack('U*')
  117. )
  118. else
  119. raise IndexError, "index #{offset} out of string"
  120. end
  121. self
  122. end
  123. # Returns +true+ if contained string contains _other_. Returns +false+ otherwise.
  124. #
  125. # Example:
  126. # Mail::Multibyte.mb_chars('Caf��').include?('��') # => true
  127. def include?(other)
  128. # We have to redefine this method because Enumerable defines it.
  129. @wrapped_string.include?(other)
  130. end
  131. # Returns the position _needle_ in the string, counting in codepoints. Returns +nil+ if _needle_ isn't found.
  132. #
  133. # Example:
  134. # Mail::Multibyte.mb_chars('Caf�� p��rifer��l').index('��') # => 12
  135. # Mail::Multibyte.mb_chars('Caf�� p��rifer��l').index(/\w/u) # => 0
  136. def index(needle, offset=0)
  137. wrapped_offset = first(offset).wrapped_string.length
  138. index = @wrapped_string.index(needle, wrapped_offset)
  139. index ? (Unicode.u_unpack(@wrapped_string.slice(0...index)).size) : nil
  140. end
  141. # Returns the position _needle_ in the string, counting in
  142. # codepoints, searching backward from _offset_ or the end of the
  143. # string. Returns +nil+ if _needle_ isn't found.
  144. #
  145. # Example:
  146. # Mail::Multibyte.mb_chars('Caf�� p��rifer��l').rindex('��') # => 6
  147. # Mail::Multibyte.mb_chars('Caf�� p��rifer��l').rindex(/\w/u) # => 13
  148. def rindex(needle, offset=nil)
  149. offset ||= length
  150. wrapped_offset = first(offset).wrapped_string.length
  151. index = @wrapped_string.rindex(needle, wrapped_offset)
  152. index ? (Unicode.u_unpack(@wrapped_string.slice(0...index)).size) : nil
  153. end
  154. # Returns the number of codepoints in the string
  155. def size
  156. Unicode.u_unpack(@wrapped_string).size
  157. end
  158. alias_method :length, :size
  159. # Strips entire range of Unicode whitespace from the right of the string.
  160. def rstrip
  161. chars(@wrapped_string.gsub(Unicode::TRAILERS_PAT, ''))
  162. end
  163. # Strips entire range of Unicode whitespace from the left of the string.
  164. def lstrip
  165. chars(@wrapped_string.gsub(Unicode::LEADERS_PAT, ''))
  166. end
  167. # Strips entire range of Unicode whitespace from the right and left of the string.
  168. def strip
  169. rstrip.lstrip
  170. end
  171. # Returns the codepoint of the first character in the string.
  172. #
  173. # Example:
  174. # Mail::Multibyte.mb_chars('���������������').ord # => 12371
  175. def ord
  176. Unicode.u_unpack(@wrapped_string)[0]
  177. end
  178. # Works just like <tt>String#rjust</tt>, only integer specifies characters instead of bytes.
  179. #
  180. # Example:
  181. #
  182. # Mail::Multibyte.mb_chars("�� cup").rjust(8).to_s
  183. # # => " �� cup"
  184. #
  185. # Mail::Multibyte.mb_chars("�� cup").rjust(8, "��").to_s # Use non-breaking whitespace
  186. # # => "�������� cup"
  187. def rjust(integer, padstr=' ')
  188. justify(integer, :right, padstr)
  189. end
  190. # Works just like <tt>String#ljust</tt>, only integer specifies characters instead of bytes.
  191. #
  192. # Example:
  193. #
  194. # Mail::Multibyte.mb_chars("�� cup").rjust(8).to_s
  195. # # => "�� cup "
  196. #
  197. # Mail::Multibyte.mb_chars("�� cup").rjust(8, "��").to_s # Use non-breaking whitespace
  198. # # => "�� cup������"
  199. def ljust(integer, padstr=' ')
  200. justify(integer, :left, padstr)
  201. end
  202. # Works just like <tt>String#center</tt>, only integer specifies characters instead of bytes.
  203. #
  204. # Example:
  205. #
  206. # Mail::Multibyte.mb_chars("�� cup").center(8).to_s
  207. # # => " �� cup "
  208. #
  209. # Mail::Multibyte.mb_chars("�� cup").center(8, "��").to_s # Use non-breaking whitespace
  210. # # => "���� cup����"
  211. def center(integer, padstr=' ')
  212. justify(integer, :center, padstr)
  213. end
  214. else
  215. 1 def =~(other)
  216. @wrapped_string =~ other
  217. end
  218. end
  219. # Works just like <tt>String#split</tt>, with the exception that the items in the resulting list are Chars
  220. # instances instead of String. This makes chaining methods easier.
  221. #
  222. # Example:
  223. # Mail::Multibyte.mb_chars('Caf�� p��rifer��l').split(/��/).map { |part| part.upcase.to_s } # => ["CAF", " P", "RIFER��L"]
  224. 1 def split(*args)
  225. @wrapped_string.split(*args).map { |i| i.mb_chars }
  226. end
  227. # Like <tt>String#[]=</tt>, except instead of byte offsets you specify character offsets.
  228. #
  229. # Example:
  230. #
  231. # s = "M��ller"
  232. # s.mb_chars[2] = "e" # Replace character with offset 2
  233. # s
  234. # # => "M��eler"
  235. #
  236. # s = "M��ller"
  237. # s.mb_chars[1, 2] = "��" # Replace 2 characters at character offset 1
  238. # s
  239. # # => "M��ler"
  240. 1 def []=(*args)
  241. replace_by = args.pop
  242. # Indexed replace with regular expressions already works
  243. if args.first.is_a?(Regexp)
  244. @wrapped_string[*args] = replace_by
  245. else
  246. result = Unicode.u_unpack(@wrapped_string)
  247. if args[0].is_a?(Integer)
  248. raise IndexError, "index #{args[0]} out of string" if args[0] >= result.length
  249. min = args[0]
  250. max = args[1].nil? ? min : (min + args[1] - 1)
  251. range = Range.new(min, max)
  252. replace_by = [replace_by].pack('U') if replace_by.is_a?(Integer)
  253. elsif args.first.is_a?(Range)
  254. raise RangeError, "#{args[0]} out of range" if args[0].min >= result.length
  255. range = args[0]
  256. else
  257. needle = args[0].to_s
  258. min = index(needle)
  259. max = min + Unicode.u_unpack(needle).length - 1
  260. range = Range.new(min, max)
  261. end
  262. result[range] = Unicode.u_unpack(replace_by)
  263. @wrapped_string.replace(result.pack('U*'))
  264. end
  265. end
  266. # Reverses all characters in the string.
  267. #
  268. # Example:
  269. # Mail::Multibyte.mb_chars('Caf��').reverse.to_s # => '��faC'
  270. 1 def reverse
  271. chars(Unicode.g_unpack(@wrapped_string).reverse.flatten.pack('U*'))
  272. end
  273. # Implements Unicode-aware slice with codepoints. Slicing on one point returns the codepoints for that
  274. # character.
  275. #
  276. # Example:
  277. # Mail::Multibyte.mb_chars('���������������').slice(2..3).to_s # => "������"
  278. 1 def slice(*args)
  279. if args.size > 2
  280. raise ArgumentError, "wrong number of arguments (#{args.size} for 1)" # Do as if we were native
  281. elsif (args.size == 2 && !(args.first.is_a?(Numeric) || args.first.is_a?(Regexp)))
  282. raise TypeError, "cannot convert #{args.first.class} into Integer" # Do as if we were native
  283. elsif (args.size == 2 && !args[1].is_a?(Numeric))
  284. raise TypeError, "cannot convert #{args[1].class} into Integer" # Do as if we were native
  285. elsif args[0].kind_of? Range
  286. cps = Unicode.u_unpack(@wrapped_string).slice(*args)
  287. result = cps.nil? ? nil : cps.pack('U*')
  288. elsif args[0].kind_of? Regexp
  289. result = @wrapped_string.slice(*args)
  290. elsif args.size == 1 && args[0].kind_of?(Numeric)
  291. character = Unicode.u_unpack(@wrapped_string)[args[0]]
  292. result = character && [character].pack('U')
  293. else
  294. cps = Unicode.u_unpack(@wrapped_string).slice(*args)
  295. result = cps && cps.pack('U*')
  296. end
  297. result && chars(result)
  298. end
  299. 1 alias_method :[], :slice
  300. # Limit the byte size of the string to a number of bytes without breaking characters. Usable
  301. # when the storage for a string is limited for some reason.
  302. #
  303. # Example:
  304. # s = '���������������'
  305. # s.mb_chars.limit(7) # => "������"
  306. 1 def limit(limit)
  307. slice(0...translate_offset(limit))
  308. end
  309. # Convert characters in the string to uppercase.
  310. #
  311. # Example:
  312. # Mail::Multibyte.mb_chars('Laurent, o�� sont les tests ?').upcase.to_s # => "LAURENT, O�� SONT LES TESTS ?"
  313. 1 def upcase
  314. chars(Unicode.apply_mapping(@wrapped_string, :uppercase_mapping))
  315. end
  316. # Convert characters in the string to lowercase.
  317. #
  318. # Example:
  319. # Mail::Multibyte.mb_chars('V��DA A V��ZKUM').downcase.to_s # => "v��da a v��zkum"
  320. 1 def downcase
  321. chars(Unicode.apply_mapping(@wrapped_string, :lowercase_mapping))
  322. end
  323. # Converts the first character to uppercase and the remainder to lowercase.
  324. #
  325. # Example:
  326. # Mail::Multibyte.mb_chars('��ber').capitalize.to_s # => "��ber"
  327. 1 def capitalize
  328. (slice(0) || chars('')).upcase + (slice(1..-1) || chars('')).downcase
  329. end
  330. # Capitalizes the first letter of every word, when possible.
  331. #
  332. # Example:
  333. # Mail::Multibyte.mb_chars("��L QUE SE ENTER��").titleize # => "��l Que Se Enter��"
  334. # Mail::Multibyte.mb_chars("���������").titleize # => "���������"
  335. 1 def titleize
  336. chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.apply_mapping $1, :uppercase_mapping })
  337. end
  338. 1 alias_method :titlecase, :titleize
  339. # Returns the KC normalization of the string by default. NFKC is considered the best normalization form for
  340. # passing strings to databases and validations.
  341. #
  342. # * <tt>form</tt> - The form you want to normalize in. Should be one of the following:
  343. # <tt>:c</tt>, <tt>:kc</tt>, <tt>:d</tt>, or <tt>:kd</tt>. Default is
  344. # Mail::Multibyte::Unicode.default_normalization_form
  345. 1 def normalize(form = nil)
  346. chars(Unicode.normalize(@wrapped_string, form))
  347. end
  348. # Performs canonical decomposition on all the characters.
  349. #
  350. # Example:
  351. # '��'.length # => 2
  352. # Mail::Multibyte.mb_chars('��').decompose.to_s.length # => 3
  353. 1 def decompose
  354. chars(Unicode.decompose_codepoints(:canonical, Unicode.u_unpack(@wrapped_string)).pack('U*'))
  355. end
  356. # Performs composition on all the characters.
  357. #
  358. # Example:
  359. # '��'.length # => 3
  360. # Mail::Multibyte.mb_chars('��').compose.to_s.length # => 2
  361. 1 def compose
  362. chars(Unicode.compose_codepoints(Unicode.u_unpack(@wrapped_string)).pack('U*'))
  363. end
  364. # Returns the number of grapheme clusters in the string.
  365. #
  366. # Example:
  367. # Mail::Multibyte.mb_chars('������������').length # => 4
  368. # Mail::Multibyte.mb_chars('������������').g_length # => 3
  369. 1 def g_length
  370. Unicode.g_unpack(@wrapped_string).length
  371. end
  372. # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent resulting in a valid UTF-8 string.
  373. #
  374. # Passing +true+ will forcibly tidy all bytes, assuming that the string's encoding is entirely CP1252 or ISO-8859-1.
  375. 1 def tidy_bytes(force = false)
  376. chars(Unicode.tidy_bytes(@wrapped_string, force))
  377. end
  378. 1 %w(capitalize downcase lstrip reverse rstrip slice strip tidy_bytes upcase).each do |method|
  379. # Only define a corresponding bang method for methods defined in the proxy; On 1.9 the proxy will
  380. # exclude lstrip!, rstrip! and strip! because they are already work as expected on multibyte strings.
  381. 9 if public_method_defined?(method)
  382. 6 define_method("#{method}!") do |*args|
  383. @wrapped_string = send(args.nil? ? method : method, *args).to_s
  384. self
  385. end
  386. end
  387. end
  388. 1 protected
  389. 1 def translate_offset(byte_offset) #:nodoc:
  390. return nil if byte_offset.nil?
  391. return 0 if @wrapped_string == ''
  392. if @wrapped_string.respond_to?(:force_encoding)
  393. @wrapped_string = @wrapped_string.dup.force_encoding(Encoding::ASCII_8BIT)
  394. end
  395. begin
  396. @wrapped_string[0...byte_offset].unpack('U*').length
  397. rescue ArgumentError
  398. byte_offset -= 1
  399. retry
  400. end
  401. end
  402. 1 def justify(integer, way, padstr=' ') #:nodoc:
  403. raise ArgumentError, "zero width padding" if padstr.length == 0
  404. padsize = integer - size
  405. padsize = padsize > 0 ? padsize : 0
  406. case way
  407. when :right
  408. result = @wrapped_string.dup.insert(0, padding(padsize, padstr))
  409. when :left
  410. result = @wrapped_string.dup.insert(-1, padding(padsize, padstr))
  411. when :center
  412. lpad = padding((padsize / 2.0).floor, padstr)
  413. rpad = padding((padsize / 2.0).ceil, padstr)
  414. result = @wrapped_string.dup.insert(0, lpad).insert(-1, rpad)
  415. end
  416. chars(result)
  417. end
  418. 1 def padding(padsize, padstr=' ') #:nodoc:
  419. if padsize != 0
  420. chars(padstr * ((padsize / Unicode.u_unpack(padstr).size) + 1)).slice(0, padsize)
  421. else
  422. ''
  423. end
  424. end
  425. 1 def chars(string) #:nodoc:
  426. self.class.new(string)
  427. end
  428. end
  429. end
  430. end

target/rubygems/gems/mail-2.7.1/lib/mail/multibyte/unicode.rb

27.23% lines covered

202 relevant lines. 55 lines covered and 147 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Mail
  3. 1 module Multibyte
  4. 1 module Unicode
  5. # Adapted from https://github.com/rails/rails/blob/master/activesupport/lib/active_support/multibyte/unicode.rb
  6. # under the MIT license
  7. # The Unicode version that is supported by the implementation
  8. 1 UNICODE_VERSION = '7.0.0'
  9. # Holds data about a codepoint in the Unicode database.
  10. 1 class Codepoint
  11. 1 attr_accessor :code, :combining_class, :decomp_type, :decomp_mapping, :uppercase_mapping, :lowercase_mapping
  12. # Initializing Codepoint object with default values
  13. 1 def initialize
  14. @combining_class = 0
  15. @uppercase_mapping = 0
  16. @lowercase_mapping = 0
  17. end
  18. 1 def swapcase_mapping
  19. uppercase_mapping > 0 ? uppercase_mapping : lowercase_mapping
  20. end
  21. end
  22. 1 extend self
  23. # A list of all available normalization forms. See http://www.unicode.org/reports/tr15/tr15-29.html for more
  24. # information about normalization.
  25. 1 NORMALIZATION_FORMS = [:c, :kc, :d, :kd]
  26. # The default normalization used for operations that require normalization. It can be set to any of the
  27. # normalizations in NORMALIZATION_FORMS.
  28. #
  29. # Example:
  30. # Mail::Multibyte::Unicode.default_normalization_form = :c
  31. 1 attr_accessor :default_normalization_form
  32. 1 @default_normalization_form = :kc
  33. # Hangul character boundaries and properties
  34. 1 HANGUL_SBASE = 0xAC00
  35. 1 HANGUL_LBASE = 0x1100
  36. 1 HANGUL_VBASE = 0x1161
  37. 1 HANGUL_TBASE = 0x11A7
  38. 1 HANGUL_LCOUNT = 19
  39. 1 HANGUL_VCOUNT = 21
  40. 1 HANGUL_TCOUNT = 28
  41. 1 HANGUL_NCOUNT = HANGUL_VCOUNT * HANGUL_TCOUNT
  42. 1 HANGUL_SCOUNT = 11172
  43. 1 HANGUL_SLAST = HANGUL_SBASE + HANGUL_SCOUNT
  44. 1 HANGUL_JAMO_FIRST = 0x1100
  45. 1 HANGUL_JAMO_LAST = 0x11FF
  46. # All the unicode whitespace
  47. WHITESPACE = [
  48. 2 (0x0009..0x000D).to_a, # White_Space # Cc [5] <control-0009>..<control-000D>
  49. 0x0020, # White_Space # Zs SPACE
  50. 0x0085, # White_Space # Cc <control-0085>
  51. 0x00A0, # White_Space # Zs NO-BREAK SPACE
  52. 0x1680, # White_Space # Zs OGHAM SPACE MARK
  53. 0x180E, # White_Space # Zs MONGOLIAN VOWEL SEPARATOR
  54. 1 (0x2000..0x200A).to_a, # White_Space # Zs [11] EN QUAD..HAIR SPACE
  55. 0x2028, # White_Space # Zl LINE SEPARATOR
  56. 0x2029, # White_Space # Zp PARAGRAPH SEPARATOR
  57. 0x202F, # White_Space # Zs NARROW NO-BREAK SPACE
  58. 0x205F, # White_Space # Zs MEDIUM MATHEMATICAL SPACE
  59. 0x3000, # White_Space # Zs IDEOGRAPHIC SPACE
  60. ].flatten.freeze
  61. # BOM (byte order mark) can also be seen as whitespace, it's a non-rendering character used to distinguish
  62. # between little and big endian. This is not an issue in utf-8, so it must be ignored.
  63. 1 LEADERS_AND_TRAILERS = WHITESPACE + [65279] # ZERO-WIDTH NO-BREAK SPACE aka BOM
  64. # Returns a regular expression pattern that matches the passed Unicode codepoints
  65. 1 def self.codepoints_to_pattern(array_of_codepoints) #:nodoc:
  66. 56 array_of_codepoints.collect{ |e| [e].pack 'U*' }.join('|')
  67. end
  68. 1 TRAILERS_PAT = /(#{codepoints_to_pattern(LEADERS_AND_TRAILERS)})+\Z/u
  69. 1 LEADERS_PAT = /\A(#{codepoints_to_pattern(LEADERS_AND_TRAILERS)})+/u
  70. # Unpack the string at codepoints boundaries. Raises an EncodingError when the encoding of the string isn't
  71. # valid UTF-8.
  72. #
  73. # Example:
  74. # Unicode.u_unpack('Caf��') # => [67, 97, 102, 233]
  75. 1 def u_unpack(string)
  76. begin
  77. string.unpack 'U*'
  78. rescue ArgumentError
  79. raise EncodingError, 'malformed UTF-8 character'
  80. end
  81. end
  82. # Detect whether the codepoint is in a certain character class. Returns +true+ when it's in the specified
  83. # character class and +false+ otherwise. Valid character classes are: <tt>:cr</tt>, <tt>:lf</tt>, <tt>:l</tt>,
  84. # <tt>:v</tt>, <tt>:lv</tt>, <tt>:lvt</tt> and <tt>:t</tt>.
  85. #
  86. # Primarily used by the grapheme cluster support.
  87. 1 def in_char_class?(codepoint, classes)
  88. classes.detect { |c| database.boundary[c] === codepoint } ? true : false
  89. end
  90. # Unpack the string at grapheme boundaries. Returns a list of character lists.
  91. #
  92. # Example:
  93. # Unicode.g_unpack('������������') # => [[2325, 2381], [2359], [2367]]
  94. # Unicode.g_unpack('Caf��') # => [[67], [97], [102], [233]]
  95. 1 def g_unpack(string)
  96. codepoints = u_unpack(string)
  97. unpacked = []
  98. pos = 0
  99. marker = 0
  100. eoc = codepoints.length
  101. while(pos < eoc)
  102. pos += 1
  103. previous = codepoints[pos-1]
  104. current = codepoints[pos]
  105. if (
  106. # CR X LF
  107. ( previous == database.boundary[:cr] and current == database.boundary[:lf] ) or
  108. # L X (L|V|LV|LVT)
  109. ( database.boundary[:l] === previous and in_char_class?(current, [:l,:v,:lv,:lvt]) ) or
  110. # (LV|V) X (V|T)
  111. ( in_char_class?(previous, [:lv,:v]) and in_char_class?(current, [:v,:t]) ) or
  112. # (LVT|T) X (T)
  113. ( in_char_class?(previous, [:lvt,:t]) and database.boundary[:t] === current ) or
  114. # X Extend
  115. (database.boundary[:extend] === current)
  116. )
  117. else
  118. unpacked << codepoints[marker..pos-1]
  119. marker = pos
  120. end
  121. end
  122. unpacked
  123. end
  124. # Reverse operation of g_unpack.
  125. #
  126. # Example:
  127. # Unicode.g_pack(Unicode.g_unpack('������������')) # => '������������'
  128. 1 def g_pack(unpacked)
  129. (unpacked.flatten).pack('U*')
  130. end
  131. # Re-order codepoints so the string becomes canonical.
  132. 1 def reorder_characters(codepoints)
  133. length = codepoints.length- 1
  134. pos = 0
  135. while pos < length do
  136. cp1, cp2 = database.codepoints[codepoints[pos]], database.codepoints[codepoints[pos+1]]
  137. if (cp1.combining_class > cp2.combining_class) && (cp2.combining_class > 0)
  138. codepoints[pos..pos+1] = cp2.code, cp1.code
  139. pos += (pos > 0 ? -1 : 1)
  140. else
  141. pos += 1
  142. end
  143. end
  144. codepoints
  145. end
  146. # Decompose composed characters to the decomposed form.
  147. 1 def decompose_codepoints(type, codepoints)
  148. codepoints.inject([]) do |decomposed, cp|
  149. # if it's a hangul syllable starter character
  150. if HANGUL_SBASE <= cp and cp < HANGUL_SLAST
  151. sindex = cp - HANGUL_SBASE
  152. ncp = [] # new codepoints
  153. ncp << HANGUL_LBASE + sindex / HANGUL_NCOUNT
  154. ncp << HANGUL_VBASE + (sindex % HANGUL_NCOUNT) / HANGUL_TCOUNT
  155. tindex = sindex % HANGUL_TCOUNT
  156. ncp << (HANGUL_TBASE + tindex) unless tindex == 0
  157. decomposed.concat ncp
  158. # if the codepoint is decomposable in with the current decomposition type
  159. elsif (ncp = database.codepoints[cp].decomp_mapping) and (!database.codepoints[cp].decomp_type || type == :compatability)
  160. decomposed.concat decompose_codepoints(type, ncp.dup)
  161. else
  162. decomposed << cp
  163. end
  164. end
  165. end
  166. # Compose decomposed characters to the composed form.
  167. 1 def compose_codepoints(codepoints)
  168. pos = 0
  169. eoa = codepoints.length - 1
  170. starter_pos = 0
  171. starter_char = codepoints[0]
  172. previous_combining_class = -1
  173. while pos < eoa
  174. pos += 1
  175. lindex = starter_char - HANGUL_LBASE
  176. # -- Hangul
  177. if 0 <= lindex and lindex < HANGUL_LCOUNT
  178. vindex = codepoints[starter_pos+1] - HANGUL_VBASE rescue vindex = -1
  179. if 0 <= vindex and vindex < HANGUL_VCOUNT
  180. tindex = codepoints[starter_pos+2] - HANGUL_TBASE rescue tindex = -1
  181. if 0 <= tindex and tindex < HANGUL_TCOUNT
  182. j = starter_pos + 2
  183. eoa -= 2
  184. else
  185. tindex = 0
  186. j = starter_pos + 1
  187. eoa -= 1
  188. end
  189. codepoints[starter_pos..j] = (lindex * HANGUL_VCOUNT + vindex) * HANGUL_TCOUNT + tindex + HANGUL_SBASE
  190. end
  191. starter_pos += 1
  192. starter_char = codepoints[starter_pos]
  193. # -- Other characters
  194. else
  195. current_char = codepoints[pos]
  196. current = database.codepoints[current_char]
  197. if current.combining_class > previous_combining_class
  198. if ref = database.composition_map[starter_char]
  199. composition = ref[current_char]
  200. else
  201. composition = nil
  202. end
  203. unless composition.nil?
  204. codepoints[starter_pos] = composition
  205. starter_char = composition
  206. codepoints.delete_at pos
  207. eoa -= 1
  208. pos -= 1
  209. previous_combining_class = -1
  210. else
  211. previous_combining_class = current.combining_class
  212. end
  213. else
  214. previous_combining_class = current.combining_class
  215. end
  216. if current.combining_class == 0
  217. starter_pos = pos
  218. starter_char = codepoints[pos]
  219. end
  220. end
  221. end
  222. codepoints
  223. end
  224. # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent resulting in a valid UTF-8 string.
  225. #
  226. # Passing +true+ will forcibly tidy all bytes, assuming that the string's encoding is entirely CP1252 or ISO-8859-1.
  227. 1 def tidy_bytes(string, force = false)
  228. if force
  229. return string.unpack("C*").map do |b|
  230. tidy_byte(b)
  231. end.flatten.compact.pack("C*").unpack("U*").pack("U*")
  232. end
  233. bytes = string.unpack("C*")
  234. conts_expected = 0
  235. last_lead = 0
  236. bytes.each_index do |i|
  237. byte = bytes[i]
  238. is_cont = byte > 127 && byte < 192
  239. is_lead = byte > 191 && byte < 245
  240. is_unused = byte > 240
  241. is_restricted = byte > 244
  242. # Impossible or highly unlikely byte? Clean it.
  243. if is_unused || is_restricted
  244. bytes[i] = tidy_byte(byte)
  245. elsif is_cont
  246. # Not expecting contination byte? Clean up. Otherwise, now expect one less.
  247. conts_expected == 0 ? bytes[i] = tidy_byte(byte) : conts_expected -= 1
  248. else
  249. if conts_expected > 0
  250. # Expected continuation, but got ASCII or leading? Clean backwards up to
  251. # the leading byte.
  252. (1..(i - last_lead)).each {|j| bytes[i - j] = tidy_byte(bytes[i - j])}
  253. conts_expected = 0
  254. end
  255. if is_lead
  256. # Final byte is leading? Clean it.
  257. if i == bytes.length - 1
  258. bytes[i] = tidy_byte(bytes.last)
  259. else
  260. # Valid leading byte? Expect continuations determined by position of
  261. # first zero bit, with max of 3.
  262. conts_expected = byte < 224 ? 1 : byte < 240 ? 2 : 3
  263. last_lead = i
  264. end
  265. end
  266. end
  267. end
  268. bytes.empty? ? "" : bytes.flatten.compact.pack("C*").unpack("U*").pack("U*")
  269. end
  270. # Returns the KC normalization of the string by default. NFKC is considered the best normalization form for
  271. # passing strings to databases and validations.
  272. #
  273. # * <tt>string</tt> - The string to perform normalization on.
  274. # * <tt>form</tt> - The form you want to normalize in. Should be one of the following:
  275. # <tt>:c</tt>, <tt>:kc</tt>, <tt>:d</tt>, or <tt>:kd</tt>. Default is
  276. # Mail::Multibyte.default_normalization_form
  277. 1 def normalize(string, form=nil)
  278. form ||= @default_normalization_form
  279. # See http://www.unicode.org/reports/tr15, Table 1
  280. codepoints = u_unpack(string)
  281. case form
  282. when :d
  283. reorder_characters(decompose_codepoints(:canonical, codepoints))
  284. when :c
  285. compose_codepoints(reorder_characters(decompose_codepoints(:canonical, codepoints)))
  286. when :kd
  287. reorder_characters(decompose_codepoints(:compatability, codepoints))
  288. when :kc
  289. compose_codepoints(reorder_characters(decompose_codepoints(:compatability, codepoints)))
  290. else
  291. raise ArgumentError, "#{form} is not a valid normalization variant", caller
  292. end.pack('U*')
  293. end
  294. 1 def apply_mapping(string, mapping) #:nodoc:
  295. u_unpack(string).map do |codepoint|
  296. cp = database.codepoints[codepoint]
  297. if cp and (ncp = cp.send(mapping)) and ncp > 0
  298. ncp
  299. else
  300. codepoint
  301. end
  302. end.pack('U*')
  303. end
  304. # Holds static data from the Unicode database
  305. 1 class UnicodeDatabase
  306. 1 ATTRIBUTES = :codepoints, :composition_exclusion, :composition_map, :boundary, :cp1252
  307. 1 attr_writer(*ATTRIBUTES)
  308. 1 def initialize
  309. @codepoints = Hash.new(Codepoint.new)
  310. @composition_exclusion = []
  311. @composition_map = {}
  312. @boundary = {}
  313. @cp1252 = {}
  314. end
  315. # Lazy load the Unicode database so it's only loaded when it's actually used
  316. 1 ATTRIBUTES.each do |attr_name|
  317. 5 class_eval(<<-EOS, __FILE__, __LINE__ + 1)
  318. 5 def #{attr_name} # def codepoints
  319. load # load
  320. 5 @#{attr_name} # @codepoints
  321. end # end
  322. EOS
  323. end
  324. # Loads the Unicode database and returns all the internal objects of UnicodeDatabase.
  325. 1 def load
  326. begin
  327. @codepoints, @composition_exclusion, @composition_map, @boundary, @cp1252 = File.open(self.class.filename, 'rb') { |f| Marshal.load f.read }
  328. rescue => e
  329. raise IOError.new("Couldn't load the Unicode tables for UTF8Handler (#{e.message}), Mail::Multibyte is unusable")
  330. end
  331. # Redefine the === method so we can write shorter rules for grapheme cluster breaks
  332. @boundary.each do |k,_|
  333. @boundary[k].instance_eval do
  334. def ===(other)
  335. detect { |i| i === other } ? true : false
  336. end
  337. end if @boundary[k].kind_of?(Array)
  338. end
  339. # define attr_reader methods for the instance variables
  340. class << self
  341. attr_reader(*ATTRIBUTES)
  342. end
  343. end
  344. # Returns the directory in which the data files are stored
  345. 1 def self.dirname
  346. File.dirname(__FILE__) + '/../values/'
  347. end
  348. # Returns the filename for the data file for this version
  349. 1 def self.filename
  350. File.expand_path File.join(dirname, "unicode_tables.dat")
  351. end
  352. end
  353. 1 private
  354. 1 def tidy_byte(byte)
  355. if byte < 160
  356. [database.cp1252[byte] || byte].pack("U").unpack("C*")
  357. elsif byte < 192
  358. [194, byte]
  359. else
  360. [195, byte - 64]
  361. end
  362. end
  363. 1 def database
  364. @database ||= UnicodeDatabase.new
  365. end
  366. end
  367. end
  368. end

target/rubygems/gems/mail-2.7.1/lib/mail/multibyte/utils.rb

36.0% lines covered

25 relevant lines. 9 lines covered and 16 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. 1 module Mail #:nodoc:
  4. 1 module Multibyte #:nodoc:
  5. 1 if RUBY_VERSION >= "1.9"
  6. # Returns a regular expression that matches valid characters in the current encoding
  7. 1 def self.valid_character
  8. VALID_CHARACTER[Encoding.default_external.to_s]
  9. end
  10. else
  11. def self.valid_character
  12. case $KCODE
  13. when 'UTF8'
  14. VALID_CHARACTER['UTF-8']
  15. when 'SJIS'
  16. VALID_CHARACTER['Shift_JIS']
  17. end
  18. end
  19. end
  20. 1 if 'string'.respond_to?(:valid_encoding?)
  21. # Verifies the encoding of a string
  22. 1 def self.verify(string)
  23. string.valid_encoding?
  24. end
  25. else
  26. def self.verify(string)
  27. if expression = valid_character
  28. # Splits the string on character boundaries, which are determined based on $KCODE.
  29. string.split(//).all? { |c| expression =~ c }
  30. else
  31. true
  32. end
  33. end
  34. end
  35. # Verifies the encoding of the string and raises an exception when it's not valid
  36. 1 def self.verify!(string)
  37. raise EncodingError.new("Found characters with invalid encoding") unless verify(string)
  38. end
  39. 1 if 'string'.respond_to?(:force_encoding)
  40. # Removes all invalid characters from the string.
  41. #
  42. # Note: this method is a no-op in Ruby 1.9
  43. 1 def self.clean(string)
  44. string
  45. end
  46. else
  47. def self.clean(string)
  48. if expression = valid_character
  49. # Splits the string on character boundaries, which are determined based on $KCODE.
  50. string.split(//).grep(expression).join
  51. else
  52. string
  53. end
  54. end
  55. end
  56. end
  57. end

target/rubygems/gems/mail-2.7.1/lib/mail/network.rb

100.0% lines covered

12 relevant lines. 12 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require 'mail/network/retriever_methods/base'
  3. 1 module Mail
  4. 1 register_autoload :SMTP, 'mail/network/delivery_methods/smtp'
  5. 1 register_autoload :FileDelivery, 'mail/network/delivery_methods/file_delivery'
  6. 1 register_autoload :LoggerDelivery, 'mail/network/delivery_methods/logger_delivery'
  7. 1 register_autoload :Sendmail, 'mail/network/delivery_methods/sendmail'
  8. 1 register_autoload :Exim, 'mail/network/delivery_methods/exim'
  9. 1 register_autoload :SMTPConnection, 'mail/network/delivery_methods/smtp_connection'
  10. 1 register_autoload :TestMailer, 'mail/network/delivery_methods/test_mailer'
  11. 1 register_autoload :POP3, 'mail/network/retriever_methods/pop3'
  12. 1 register_autoload :IMAP, 'mail/network/retriever_methods/imap'
  13. 1 register_autoload :TestRetriever, 'mail/network/retriever_methods/test_retriever'
  14. end

target/rubygems/gems/mail-2.7.1/lib/mail/network/delivery_methods/file_delivery.rb

50.0% lines covered

16 relevant lines. 8 lines covered and 8 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require 'mail/check_delivery_params'
  3. 1 module Mail
  4. # FileDelivery class delivers emails into multiple files based on the destination
  5. # address. Each file is appended to if it already exists.
  6. #
  7. # So if you have an email going to fred@test, bob@test, joe@anothertest, and you
  8. # set your location path to /path/to/mails then FileDelivery will create the directory
  9. # if it does not exist, and put one copy of the email in three files, called
  10. # by their message id
  11. #
  12. # Make sure the path you specify with :location is writable by the Ruby process
  13. # running Mail.
  14. 1 class FileDelivery
  15. 1 if RUBY_VERSION >= '1.9.1'
  16. 1 require 'fileutils'
  17. else
  18. require 'ftools'
  19. end
  20. 1 attr_accessor :settings
  21. 1 def initialize(values)
  22. self.settings = { :location => './mails' }.merge!(values)
  23. end
  24. 1 def deliver!(mail)
  25. Mail::CheckDeliveryParams.check(mail)
  26. if ::File.respond_to?(:makedirs)
  27. ::File.makedirs settings[:location]
  28. else
  29. ::FileUtils.mkdir_p settings[:location]
  30. end
  31. mail.destinations.uniq.each do |to|
  32. ::File.open(::File.join(settings[:location], File.basename(to.to_s)), 'a') { |f| "#{f.write(mail.encoded)}\r\n\r\n" }
  33. end
  34. end
  35. end
  36. end

target/rubygems/gems/mail-2.7.1/lib/mail/network/delivery_methods/sendmail.rb

44.0% lines covered

25 relevant lines. 11 lines covered and 14 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require 'mail/check_delivery_params'
  3. 1 module Mail
  4. # A delivery method implementation which sends via sendmail.
  5. #
  6. # To use this, first find out where the sendmail binary is on your computer,
  7. # if you are on a mac or unix box, it is usually in /usr/sbin/sendmail, this will
  8. # be your sendmail location.
  9. #
  10. # Mail.defaults do
  11. # delivery_method :sendmail
  12. # end
  13. #
  14. # Or if your sendmail binary is not at '/usr/sbin/sendmail'
  15. #
  16. # Mail.defaults do
  17. # delivery_method :sendmail, :location => '/absolute/path/to/your/sendmail'
  18. # end
  19. #
  20. # Then just deliver the email as normal:
  21. #
  22. # Mail.deliver do
  23. # to 'mikel@test.lindsaar.net'
  24. # from 'ada@test.lindsaar.net'
  25. # subject 'testing sendmail'
  26. # body 'testing sendmail'
  27. # end
  28. #
  29. # Or by calling deliver on a Mail message
  30. #
  31. # mail = Mail.new do
  32. # to 'mikel@test.lindsaar.net'
  33. # from 'ada@test.lindsaar.net'
  34. # subject 'testing sendmail'
  35. # body 'testing sendmail'
  36. # end
  37. #
  38. # mail.deliver!
  39. 1 class Sendmail
  40. 1 DEFAULTS = {
  41. :location => '/usr/sbin/sendmail',
  42. :arguments => '-i'
  43. }
  44. 1 attr_accessor :settings
  45. 1 def initialize(values)
  46. self.settings = self.class::DEFAULTS.merge(values)
  47. end
  48. 1 def deliver!(mail)
  49. smtp_from, smtp_to, message = Mail::CheckDeliveryParams.check(mail)
  50. from = "-f #{self.class.shellquote(smtp_from)}" if smtp_from
  51. to = smtp_to.map { |_to| self.class.shellquote(_to) }.join(' ')
  52. arguments = "#{settings[:arguments]} #{from} --"
  53. self.class.call(settings[:location], arguments, to, message)
  54. end
  55. 1 def self.call(path, arguments, destinations, encoded_message)
  56. popen "#{path} #{arguments} #{destinations}" do |io|
  57. io.puts ::Mail::Utilities.binary_unsafe_to_lf(encoded_message)
  58. io.flush
  59. end
  60. end
  61. 1 if RUBY_VERSION < '1.9.0'
  62. def self.popen(command, &block)
  63. IO.popen "#{command} 2>&1", 'w+', &block
  64. end
  65. else
  66. 1 def self.popen(command, &block)
  67. IO.popen command, 'w+', :err => :out, &block
  68. end
  69. end
  70. # The following is an adaptation of ruby 1.9.2's shellwords.rb file,
  71. # with the following modifications:
  72. #
  73. # - Wraps in double quotes
  74. # - Allows '+' to accept email addresses with them
  75. # - Allows '~' as it is not unescaped in double quotes
  76. 1 def self.shellquote(address)
  77. # Process as a single byte sequence because not all shell
  78. # implementations are multibyte aware.
  79. #
  80. # A LF cannot be escaped with a backslash because a backslash + LF
  81. # combo is regarded as line continuation and simply ignored. Strip it.
  82. escaped = address.gsub(/([^A-Za-z0-9_\s\+\-.,:\/@~])/n, "\\\\\\1").gsub("\n", '')
  83. %("#{escaped}")
  84. end
  85. end
  86. end

target/rubygems/gems/mail-2.7.1/lib/mail/network/delivery_methods/smtp.rb

32.35% lines covered

34 relevant lines. 11 lines covered and 23 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require 'mail/check_delivery_params'
  3. 1 module Mail
  4. # == Sending Email with SMTP
  5. #
  6. # Mail allows you to send emails using SMTP. This is done by wrapping Net::SMTP in
  7. # an easy to use manner.
  8. #
  9. # === Sending via SMTP server on Localhost
  10. #
  11. # Sending locally (to a postfix or sendmail server running on localhost) requires
  12. # no special setup. Just to Mail.deliver &block or message.deliver! and it will
  13. # be sent in this method.
  14. #
  15. # === Sending via MobileMe
  16. #
  17. # Mail.defaults do
  18. # delivery_method :smtp, { :address => "smtp.me.com",
  19. # :port => 587,
  20. # :domain => 'your.host.name',
  21. # :user_name => '<username>',
  22. # :password => '<password>',
  23. # :authentication => 'plain',
  24. # :enable_starttls_auto => true }
  25. # end
  26. #
  27. # === Sending via GMail
  28. #
  29. # Mail.defaults do
  30. # delivery_method :smtp, { :address => "smtp.gmail.com",
  31. # :port => 587,
  32. # :domain => 'your.host.name',
  33. # :user_name => '<username>',
  34. # :password => '<password>',
  35. # :authentication => 'plain',
  36. # :enable_starttls_auto => true }
  37. # end
  38. #
  39. # === Certificate verification
  40. #
  41. # When using TLS, some mail servers provide certificates that are self-signed
  42. # or whose names do not exactly match the hostname given in the address.
  43. # OpenSSL will reject these by default. The best remedy is to use the correct
  44. # hostname or update the certificate authorities trusted by your ruby. If
  45. # that isn't possible, you can control this behavior with
  46. # an :openssl_verify_mode setting. Its value may be either an OpenSSL
  47. # verify mode constant (OpenSSL::SSL::VERIFY_NONE, OpenSSL::SSL::VERIFY_PEER),
  48. # or a string containing the name of an OpenSSL verify mode (none, peer).
  49. #
  50. # === Others
  51. #
  52. # Feel free to send me other examples that were tricky
  53. #
  54. # === Delivering the email
  55. #
  56. # Once you have the settings right, sending the email is done by:
  57. #
  58. # Mail.deliver do
  59. # to 'mikel@test.lindsaar.net'
  60. # from 'ada@test.lindsaar.net'
  61. # subject 'testing sendmail'
  62. # body 'testing sendmail'
  63. # end
  64. #
  65. # Or by calling deliver on a Mail message
  66. #
  67. # mail = Mail.new do
  68. # to 'mikel@test.lindsaar.net'
  69. # from 'ada@test.lindsaar.net'
  70. # subject 'testing sendmail'
  71. # body 'testing sendmail'
  72. # end
  73. #
  74. # mail.deliver!
  75. 1 class SMTP
  76. 1 attr_accessor :settings
  77. 1 DEFAULTS = {
  78. :address => 'localhost',
  79. :port => 25,
  80. :domain => 'localhost.localdomain',
  81. :user_name => nil,
  82. :password => nil,
  83. :authentication => nil,
  84. :enable_starttls => nil,
  85. :enable_starttls_auto => true,
  86. :openssl_verify_mode => nil,
  87. :ssl => nil,
  88. :tls => nil,
  89. :open_timeout => nil,
  90. :read_timeout => nil
  91. }
  92. 1 def initialize(values)
  93. self.settings = DEFAULTS.merge(values)
  94. end
  95. 1 def deliver!(mail)
  96. response = start_smtp_session do |smtp|
  97. Mail::SMTPConnection.new(:connection => smtp, :return_response => true).deliver!(mail)
  98. end
  99. settings[:return_response] ? response : self
  100. end
  101. 1 private
  102. 1 def start_smtp_session(&block)
  103. build_smtp_session.start(settings[:domain], settings[:user_name], settings[:password], settings[:authentication], &block)
  104. end
  105. 1 def build_smtp_session
  106. Net::SMTP.new(settings[:address], settings[:port]).tap do |smtp|
  107. if settings[:tls] || settings[:ssl]
  108. if smtp.respond_to?(:enable_tls)
  109. smtp.enable_tls(ssl_context)
  110. end
  111. elsif settings[:enable_starttls]
  112. if smtp.respond_to?(:enable_starttls)
  113. smtp.enable_starttls(ssl_context)
  114. end
  115. elsif settings[:enable_starttls_auto]
  116. if smtp.respond_to?(:enable_starttls_auto)
  117. smtp.enable_starttls_auto(ssl_context)
  118. end
  119. end
  120. smtp.open_timeout = settings[:open_timeout] if settings[:open_timeout]
  121. smtp.read_timeout = settings[:read_timeout] if settings[:read_timeout]
  122. end
  123. end
  124. # Allow SSL context to be configured via settings, for Ruby >= 1.9
  125. # Just returns openssl verify mode for Ruby 1.8.x
  126. 1 def ssl_context
  127. openssl_verify_mode = settings[:openssl_verify_mode]
  128. if openssl_verify_mode.kind_of?(String)
  129. openssl_verify_mode = OpenSSL::SSL.const_get("VERIFY_#{openssl_verify_mode.upcase}")
  130. end
  131. context = Net::SMTP.default_ssl_context
  132. context.verify_mode = openssl_verify_mode if openssl_verify_mode
  133. context.ca_path = settings[:ca_path] if settings[:ca_path]
  134. context.ca_file = settings[:ca_file] if settings[:ca_file]
  135. context
  136. end
  137. end
  138. end

target/rubygems/gems/mail-2.7.1/lib/mail/network/delivery_methods/test_mailer.rb

61.54% lines covered

13 relevant lines. 8 lines covered and 5 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require 'mail/check_delivery_params'
  3. 1 module Mail
  4. # The TestMailer is a bare bones mailer that does nothing. It is useful
  5. # when you are testing.
  6. #
  7. # It also provides a template of the minimum methods you require to implement
  8. # if you want to make a custom mailer for Mail
  9. 1 class TestMailer
  10. # Provides a store of all the emails sent with the TestMailer so you can check them.
  11. 1 def self.deliveries
  12. @@deliveries ||= []
  13. end
  14. # Allows you to over write the default deliveries store from an array to some
  15. # other object. If you just want to clear the store,
  16. # call TestMailer.deliveries.clear.
  17. #
  18. # If you place another object here, please make sure it responds to:
  19. #
  20. # * << (message)
  21. # * clear
  22. # * length
  23. # * size
  24. # * and other common Array methods
  25. 1 def self.deliveries=(val)
  26. @@deliveries = val
  27. end
  28. 1 attr_accessor :settings
  29. 1 def initialize(values)
  30. @settings = values.dup
  31. end
  32. 1 def deliver!(mail)
  33. Mail::CheckDeliveryParams.check(mail)
  34. Mail::TestMailer.deliveries << mail
  35. end
  36. end
  37. end

target/rubygems/gems/mail-2.7.1/lib/mail/network/retriever_methods/base.rb

30.0% lines covered

20 relevant lines. 6 lines covered and 14 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. 1 module Mail
  4. 1 class Retriever
  5. # Get the oldest received email(s)
  6. #
  7. # Possible options:
  8. # count: number of emails to retrieve. The default value is 1.
  9. # order: order of emails returned. Possible values are :asc or :desc. Default value is :asc.
  10. #
  11. 1 def first(options = {}, &block)
  12. options ||= {}
  13. options[:what] = :first
  14. options[:count] ||= 1
  15. find(options, &block)
  16. end
  17. # Get the most recent received email(s)
  18. #
  19. # Possible options:
  20. # count: number of emails to retrieve. The default value is 1.
  21. # order: order of emails returned. Possible values are :asc or :desc. Default value is :asc.
  22. #
  23. 1 def last(options = {}, &block)
  24. options ||= {}
  25. options[:what] = :last
  26. options[:count] ||= 1
  27. find(options, &block)
  28. end
  29. # Get all emails.
  30. #
  31. # Possible options:
  32. # order: order of emails returned. Possible values are :asc or :desc. Default value is :asc.
  33. #
  34. 1 def all(options = {}, &block)
  35. options ||= {}
  36. options[:count] = :all
  37. find(options, &block)
  38. end
  39. # Find emails in the mailbox, and then deletes them. Without any options, the
  40. # five last received emails are returned.
  41. #
  42. # Possible options:
  43. # what: last or first emails. The default is :first.
  44. # order: order of emails returned. Possible values are :asc or :desc. Default value is :asc.
  45. # count: number of emails to retrieve. The default value is 10. A value of 1 returns an
  46. # instance of Message, not an array of Message instances.
  47. # delete_after_find: flag for whether to delete each retreived email after find. Default
  48. # is true. Call #find if you would like this to default to false.
  49. #
  50. 1 def find_and_delete(options = {}, &block)
  51. options ||= {}
  52. options[:delete_after_find] ||= true
  53. find(options, &block)
  54. end
  55. end
  56. end

target/rubygems/gems/mail-2.7.1/lib/mail/part.rb

41.82% lines covered

55 relevant lines. 23 lines covered and 32 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. 1 module Mail
  4. 1 class Part < Message
  5. # Creates a new empty Content-ID field and inserts it in the correct order
  6. # into the Header. The ContentIdField object will automatically generate
  7. # a unique content ID if you try and encode it or output it to_s without
  8. # specifying a content id.
  9. #
  10. # It will preserve the content ID you specify if you do.
  11. 1 def add_content_id(content_id_val = '')
  12. header['content-id'] = content_id_val
  13. end
  14. # Returns true if the part has a content ID field, the field may or may
  15. # not have a value, but the field exists or not.
  16. 1 def has_content_id?
  17. header.has_content_id?
  18. end
  19. 1 def inline_content_id
  20. # TODO: Deprecated in 2.2.2 - Remove in 2.3
  21. warn("Part#inline_content_id is deprecated, please call Part#cid instead")
  22. cid
  23. end
  24. 1 def cid
  25. add_content_id unless has_content_id?
  26. uri_escape(unbracket(content_id))
  27. end
  28. 1 def url
  29. "cid:#{cid}"
  30. end
  31. 1 def inline?
  32. header[:content_disposition].disposition_type == 'inline' if header[:content_disposition].respond_to?(:disposition_type)
  33. end
  34. 1 def add_required_fields
  35. super
  36. add_content_id if !has_content_id? && inline?
  37. end
  38. 1 def add_required_message_fields
  39. # Override so we don't add Date, MIME-Version, or Message-ID.
  40. end
  41. 1 def delivery_status_report_part?
  42. (main_type =~ /message/i && sub_type =~ /delivery-status/i) && body =~ /Status:/
  43. end
  44. 1 def delivery_status_data
  45. delivery_status_report_part? ? parse_delivery_status_report : {}
  46. end
  47. 1 def bounced?
  48. if action.is_a?(Array)
  49. !!(action.first =~ /failed/i)
  50. else
  51. !!(action =~ /failed/i)
  52. end
  53. end
  54. # Either returns the action if the message has just a single report, or an
  55. # array of all the actions, one for each report
  56. 1 def action
  57. get_return_values('action')
  58. end
  59. 1 def final_recipient
  60. get_return_values('final-recipient')
  61. end
  62. 1 def error_status
  63. get_return_values('status')
  64. end
  65. 1 def diagnostic_code
  66. get_return_values('diagnostic-code')
  67. end
  68. 1 def remote_mta
  69. get_return_values('remote-mta')
  70. end
  71. 1 def retryable?
  72. !(error_status =~ /^5/)
  73. end
  74. 1 private
  75. 1 def get_return_values(key)
  76. if delivery_status_data[key].is_a?(Array)
  77. delivery_status_data[key].map { |a| a.value }
  78. elsif !delivery_status_data[key].nil?
  79. delivery_status_data[key].value
  80. else
  81. nil
  82. end
  83. end
  84. # A part may not have a header.... so, just init a body if no header
  85. 1 def parse_message
  86. header_part, body_part = raw_source.split(/#{Constants::CRLF}#{Constants::WSP}*#{Constants::CRLF}/m, 2)
  87. if header_part =~ Constants::HEADER_LINE
  88. self.header = header_part
  89. self.body = body_part
  90. else
  91. self.header = "Content-Type: text/plain\r\n"
  92. self.body = raw_source
  93. end
  94. end
  95. 1 def parse_delivery_status_report
  96. @delivery_status_data ||= Header.new(body.to_s.gsub("\r\n\r\n", "\r\n"))
  97. end
  98. end
  99. end

target/rubygems/gems/mail-2.7.1/lib/mail/parts_list.rb

43.24% lines covered

37 relevant lines. 16 lines covered and 21 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require 'delegate'
  3. 1 module Mail
  4. 1 class PartsList < DelegateClass(Array)
  5. 1 attr_reader :parts
  6. 1 def initialize(*args)
  7. @parts = Array.new(*args)
  8. super @parts
  9. end
  10. # The #encode_with and #to_yaml methods are just implemented
  11. # for the sake of backward compatibility ; the delegator does
  12. # not correctly delegate these calls to the delegated object
  13. 1 def encode_with(coder) # :nodoc:
  14. coder.represent_object(nil, @parts)
  15. end
  16. 1 def to_yaml(options = {}) # :nodoc:
  17. @parts.to_yaml(options)
  18. end
  19. 1 def attachments
  20. Mail::AttachmentsList.new(@parts)
  21. end
  22. 1 def collect
  23. if block_given?
  24. ary = PartsList.new
  25. each { |o| ary << yield(o) }
  26. ary
  27. else
  28. to_a
  29. end
  30. end
  31. 1 alias_method :map, :collect
  32. 1 def map!
  33. raise NoMethodError, "#map! is not defined, please call #collect and create a new PartsList"
  34. end
  35. 1 def collect!
  36. raise NoMethodError, "#collect! is not defined, please call #collect and create a new PartsList"
  37. end
  38. 1 def sort
  39. self.class.new(@parts.sort)
  40. end
  41. 1 def sort!(order)
  42. # stable sort should be used to maintain the relative order as the parts are added
  43. i = 0;
  44. sorted = @parts.sort_by do |a|
  45. # OK, 10000 is arbitrary... if anyone actually wants to explicitly sort 10000 parts of a
  46. # single email message... please show me a use case and I'll put more work into this method,
  47. # in the meantime, it works :)
  48. get_order_value(a, order) << (i += 1)
  49. end
  50. @parts.clear
  51. sorted.each { |p| @parts << p }
  52. end
  53. 1 private
  54. 1 def get_order_value(part, order)
  55. is_attachment = part.respond_to?(:attachment?) && part.attachment?
  56. has_content_type = part.respond_to?(:content_type) && !part[:content_type].nil?
  57. [is_attachment ? 1 : 0, (has_content_type ? order.index(part[:content_type].string.downcase) : nil) || 10000]
  58. end
  59. end
  60. end

target/rubygems/gems/mail-2.7.1/lib/mail/utilities.rb

36.94% lines covered

111 relevant lines. 41 lines covered and 70 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. 1 require 'mail/constants'
  4. 1 module Mail
  5. 1 module Utilities
  6. 1 LF = "\n"
  7. 1 CRLF = "\r\n"
  8. 1 include Constants
  9. # Returns true if the string supplied is free from characters not allowed as an ATOM
  10. 1 def atom_safe?( str )
  11. not ATOM_UNSAFE === str
  12. end
  13. # If the string supplied has ATOM unsafe characters in it, will return the string quoted
  14. # in double quotes, otherwise returns the string unmodified
  15. 1 def quote_atom( str )
  16. atom_safe?( str ) ? str : dquote(str)
  17. end
  18. # If the string supplied has PHRASE unsafe characters in it, will return the string quoted
  19. # in double quotes, otherwise returns the string unmodified
  20. 1 def quote_phrase( str )
  21. if str.respond_to?(:force_encoding)
  22. original_encoding = str.encoding
  23. ascii_str = str.to_s.dup.force_encoding('ASCII-8BIT')
  24. if (PHRASE_UNSAFE === ascii_str)
  25. dquote(ascii_str).force_encoding(original_encoding)
  26. else
  27. str
  28. end
  29. else
  30. (PHRASE_UNSAFE === str) ? dquote(str) : str
  31. end
  32. end
  33. # Returns true if the string supplied is free from characters not allowed as a TOKEN
  34. 1 def token_safe?( str )
  35. not TOKEN_UNSAFE === str
  36. end
  37. # If the string supplied has TOKEN unsafe characters in it, will return the string quoted
  38. # in double quotes, otherwise returns the string unmodified
  39. 1 def quote_token( str )
  40. if str.respond_to?(:force_encoding)
  41. original_encoding = str.encoding
  42. ascii_str = str.to_s.dup.force_encoding('ASCII-8BIT')
  43. if token_safe?( ascii_str )
  44. str
  45. else
  46. dquote(ascii_str).force_encoding(original_encoding)
  47. end
  48. else
  49. token_safe?( str ) ? str : dquote(str)
  50. end
  51. end
  52. # Wraps supplied string in double quotes and applies \-escaping as necessary,
  53. # unless it is already wrapped.
  54. #
  55. # Example:
  56. #
  57. # string = 'This is a string'
  58. # dquote(string) #=> '"This is a string"'
  59. #
  60. # string = 'This is "a string"'
  61. # dquote(string #=> '"This is \"a string\"'
  62. 1 def dquote( str )
  63. '"' + unquote(str).gsub(/[\\"]/n) {|s| '\\' + s } + '"'
  64. end
  65. # Unwraps supplied string from inside double quotes and
  66. # removes any \-escaping.
  67. #
  68. # Example:
  69. #
  70. # string = '"This is a string"'
  71. # unquote(string) #=> 'This is a string'
  72. #
  73. # string = '"This is \"a string\""'
  74. # unqoute(string) #=> 'This is "a string"'
  75. 1 def unquote( str )
  76. if str =~ /^"(.*?)"$/
  77. unescape($1)
  78. else
  79. str
  80. end
  81. end
  82. 1 module_function :unquote
  83. # Removes any \-escaping.
  84. #
  85. # Example:
  86. #
  87. # string = 'This is \"a string\"'
  88. # unescape(string) #=> 'This is "a string"'
  89. #
  90. # string = '"This is \"a string\""'
  91. # unescape(string) #=> '"This is "a string""'
  92. 1 def unescape( str )
  93. str.gsub(/\\(.)/, '\1')
  94. end
  95. 1 module_function :unescape
  96. # Wraps a string in parenthesis and escapes any that are in the string itself.
  97. #
  98. # Example:
  99. #
  100. # paren( 'This is a string' ) #=> '(This is a string)'
  101. 1 def paren( str )
  102. RubyVer.paren( str )
  103. end
  104. # Unwraps a string from being wrapped in parenthesis
  105. #
  106. # Example:
  107. #
  108. # str = '(This is a string)'
  109. # unparen( str ) #=> 'This is a string'
  110. 1 def unparen( str )
  111. match = str.match(/^\((.*?)\)$/)
  112. match ? match[1] : str
  113. end
  114. # Wraps a string in angle brackets and escapes any that are in the string itself
  115. #
  116. # Example:
  117. #
  118. # bracket( 'This is a string' ) #=> '<This is a string>'
  119. 1 def bracket( str )
  120. RubyVer.bracket( str )
  121. end
  122. # Unwraps a string from being wrapped in parenthesis
  123. #
  124. # Example:
  125. #
  126. # str = '<This is a string>'
  127. # unbracket( str ) #=> 'This is a string'
  128. 1 def unbracket( str )
  129. match = str.match(/^\<(.*?)\>$/)
  130. match ? match[1] : str
  131. end
  132. # Escape parenthesies in a string
  133. #
  134. # Example:
  135. #
  136. # str = 'This is (a) string'
  137. # escape_paren( str ) #=> 'This is \(a\) string'
  138. 1 def escape_paren( str )
  139. RubyVer.escape_paren( str )
  140. end
  141. 1 def uri_escape( str )
  142. uri_parser.escape(str)
  143. end
  144. 1 def uri_unescape( str )
  145. uri_parser.unescape(str)
  146. end
  147. 1 def uri_parser
  148. @uri_parser ||= URI.const_defined?(:Parser) ? URI::Parser.new : URI
  149. end
  150. # Matches two objects with their to_s values case insensitively
  151. #
  152. # Example:
  153. #
  154. # obj2 = "This_is_An_object"
  155. # obj1 = :this_IS_an_object
  156. # match_to_s( obj1, obj2 ) #=> true
  157. 1 def match_to_s( obj1, obj2 )
  158. obj1.to_s.casecmp(obj2.to_s) == 0
  159. end
  160. # Capitalizes a string that is joined by hyphens correctly.
  161. #
  162. # Example:
  163. #
  164. # string = 'resent-from-field'
  165. # capitalize_field( string ) #=> 'Resent-From-Field'
  166. 1 def capitalize_field( str )
  167. str.to_s.split("-").map { |v| v.capitalize }.join("-")
  168. end
  169. # Takes an underscored word and turns it into a class name
  170. #
  171. # Example:
  172. #
  173. # constantize("hello") #=> "Hello"
  174. # constantize("hello-there") #=> "HelloThere"
  175. # constantize("hello-there-mate") #=> "HelloThereMate"
  176. 1 def constantize( str )
  177. str.to_s.split(/[-_]/).map { |v| v.capitalize }.to_s
  178. end
  179. # Swaps out all underscores (_) for hyphens (-) good for stringing from symbols
  180. # a field name.
  181. #
  182. # Example:
  183. #
  184. # string = :resent_from_field
  185. # dasherize( string ) #=> 'resent-from-field'
  186. 1 def dasherize( str )
  187. str.to_s.tr(UNDERSCORE, HYPHEN)
  188. end
  189. # Swaps out all hyphens (-) for underscores (_) good for stringing to symbols
  190. # a field name.
  191. #
  192. # Example:
  193. #
  194. # string = :resent_from_field
  195. # underscoreize ( string ) #=> 'resent_from_field'
  196. 1 def underscoreize( str )
  197. 8 str.to_s.downcase.tr(HYPHEN, UNDERSCORE)
  198. end
  199. 1 if RUBY_VERSION <= '1.8.6'
  200. def map_lines( str, &block )
  201. results = []
  202. str.each_line do |line|
  203. results << yield(line)
  204. end
  205. results
  206. end
  207. def map_with_index( enum, &block )
  208. results = []
  209. enum.each_with_index do |token, i|
  210. results[i] = yield(token, i)
  211. end
  212. results
  213. end
  214. else
  215. 1 def map_lines( str, &block )
  216. str.each_line.map(&block)
  217. end
  218. 1 def map_with_index( enum, &block )
  219. enum.each_with_index.map(&block)
  220. end
  221. end
  222. 1 def self.binary_unsafe_to_lf(string) #:nodoc:
  223. string.gsub(/\r\n|\r/, LF)
  224. end
  225. TO_CRLF_REGEX =
  226. if RUBY_VERSION >= '1.9'
  227. # This 1.9 only regex can save a reasonable amount of time (~20%)
  228. # by not matching "\r\n" so the string is returned unchanged in
  229. # the common case.
  230. 1 Regexp.new("(?<!\r)\n|\r(?!\n)")
  231. else
  232. /\n|\r\n|\r/
  233. end
  234. 1 def self.binary_unsafe_to_crlf(string) #:nodoc:
  235. string.gsub(TO_CRLF_REGEX, CRLF)
  236. end
  237. 1 if RUBY_VERSION < '1.9'
  238. def self.safe_for_line_ending_conversion?(string) #:nodoc:
  239. string.ascii_only?
  240. end
  241. else
  242. 1 def self.safe_for_line_ending_conversion?(string) #:nodoc:
  243. if string.encoding == Encoding::BINARY
  244. string.ascii_only?
  245. else
  246. string.valid_encoding?
  247. end
  248. end
  249. end
  250. # Convert line endings to \n unless the string is binary. Used for
  251. # sendmail delivery and for decoding 8bit Content-Transfer-Encoding.
  252. 1 def self.to_lf(string)
  253. string = string.to_s
  254. if safe_for_line_ending_conversion? string
  255. binary_unsafe_to_lf string
  256. else
  257. string
  258. end
  259. end
  260. # Convert line endings to \r\n unless the string is binary. Used for
  261. # encoding 8bit and base64 Content-Transfer-Encoding and for convenience
  262. # when parsing emails with \n line endings instead of the required \r\n.
  263. 1 def self.to_crlf(string)
  264. string = string.to_s
  265. if safe_for_line_ending_conversion? string
  266. binary_unsafe_to_crlf string
  267. else
  268. string
  269. end
  270. end
  271. # Returns true if the object is considered blank.
  272. # A blank includes things like '', ' ', nil,
  273. # and arrays and hashes that have nothing in them.
  274. #
  275. # This logic is mostly shared with ActiveSupport's blank?
  276. 1 def self.blank?(value)
  277. if value.kind_of?(NilClass)
  278. true
  279. elsif value.kind_of?(String)
  280. value !~ /\S/
  281. else
  282. value.respond_to?(:empty?) ? value.empty? : !value
  283. end
  284. end
  285. end
  286. end

target/rubygems/gems/mail-2.7.1/lib/mail/version.rb

88.89% lines covered

9 relevant lines. 8 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Mail
  3. 1 module VERSION
  4. 1 MAJOR = 2
  5. 1 MINOR = 7
  6. 1 PATCH = 1
  7. 1 BUILD = nil
  8. 1 STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join('.')
  9. 1 def self.version
  10. STRING
  11. end
  12. end
  13. end

target/rubygems/gems/mail-2.7.1/lib/mail/version_specific/ruby_1_9.rb

26.67% lines covered

135 relevant lines. 36 lines covered and 99 lines missed.
    
  1. # encoding: utf-8
  2. # frozen_string_literal: true
  3. 1 module Mail
  4. 1 class Ruby19
  5. 1 class StrictCharsetEncoder
  6. 1 def encode(string, charset)
  7. case charset
  8. when /utf-?7/i
  9. Mail::Ruby19.decode_utf7(string)
  10. else
  11. string.force_encoding(Mail::Ruby19.pick_encoding(charset))
  12. end
  13. end
  14. end
  15. 1 class BestEffortCharsetEncoder
  16. 1 def encode(string, charset)
  17. case charset
  18. when /utf-?7/i
  19. Mail::Ruby19.decode_utf7(string)
  20. else
  21. string.force_encoding(pick_encoding(charset))
  22. end
  23. end
  24. 1 private
  25. 1 def pick_encoding(charset)
  26. charset = case charset
  27. when /ansi_x3.110-1983/
  28. 'ISO-8859-1'
  29. when /Windows-?1258/i # Windows-1258 is similar to 1252
  30. "Windows-1252"
  31. else
  32. charset
  33. end
  34. Mail::Ruby19.pick_encoding(charset)
  35. end
  36. end
  37. 1 class << self
  38. 1 attr_accessor :charset_encoder
  39. end
  40. 1 self.charset_encoder = BestEffortCharsetEncoder.new
  41. # Escapes any parenthesis in a string that are unescaped this uses
  42. # a Ruby 1.9.1 regexp feature of negative look behind
  43. 1 def Ruby19.escape_paren( str )
  44. re = /(?<!\\)([\(\)])/ # Only match unescaped parens
  45. str.gsub(re) { |s| '\\' + s }
  46. end
  47. 1 def Ruby19.paren( str )
  48. str = $1 if str =~ /^\((.*)?\)$/
  49. str = escape_paren( str )
  50. '(' + str + ')'
  51. end
  52. 1 def Ruby19.escape_bracket( str )
  53. re = /(?<!\\)([\<\>])/ # Only match unescaped brackets
  54. str.gsub(re) { |s| '\\' + s }
  55. end
  56. 1 def Ruby19.bracket( str )
  57. str = $1 if str =~ /^\<(.*)?\>$/
  58. str = escape_bracket( str )
  59. '<' + str + '>'
  60. end
  61. 1 def Ruby19.decode_base64(str)
  62. if !str.end_with?("=") && str.length % 4 != 0
  63. str = str.ljust((str.length + 3) & ~3, "=")
  64. end
  65. str.unpack( 'm' ).first
  66. end
  67. 1 def Ruby19.encode_base64(str)
  68. [str].pack( 'm' )
  69. end
  70. 1 def Ruby19.has_constant?(klass, string)
  71. klass.const_defined?( string, false )
  72. end
  73. 1 def Ruby19.get_constant(klass, string)
  74. klass.const_get( string )
  75. end
  76. 1 def Ruby19.transcode_charset(str, from_encoding, to_encoding = Encoding::UTF_8)
  77. to_encoding = to_encoding.to_s if RUBY_VERSION < '1.9.3'
  78. to_encoding = Encoding.find(to_encoding)
  79. replacement_char = to_encoding == Encoding::UTF_8 ? '���' : '?'
  80. charset_encoder.encode(str.dup, from_encoding).encode(to_encoding, :undef => :replace, :invalid => :replace, :replace => replacement_char)
  81. end
  82. # From Ruby stdlib Net::IMAP
  83. 1 def Ruby19.encode_utf7(string)
  84. string.gsub(/(&)|[^\x20-\x7e]+/) do
  85. if $1
  86. "&-"
  87. else
  88. base64 = [$&.encode(Encoding::UTF_16BE)].pack("m0")
  89. "&" + base64.delete("=").tr("/", ",") + "-"
  90. end
  91. end.force_encoding(Encoding::ASCII_8BIT)
  92. end
  93. 1 def Ruby19.decode_utf7(utf7)
  94. utf7.gsub(/&([^-]+)?-/n) do
  95. if $1
  96. ($1.tr(",", "/") + "===").unpack("m")[0].encode(Encoding::UTF_8, Encoding::UTF_16BE)
  97. else
  98. "&"
  99. end
  100. end
  101. end
  102. 1 def Ruby19.b_value_encode(str, encoding = nil)
  103. encoding = str.encoding.to_s
  104. [Ruby19.encode_base64(str), encoding]
  105. end
  106. 1 def Ruby19.b_value_decode(str)
  107. match = str.match(/\=\?(.+)?\?[Bb]\?(.*)\?\=/m)
  108. if match
  109. charset = match[1]
  110. str = Ruby19.decode_base64(match[2])
  111. str = charset_encoder.encode(str, charset)
  112. end
  113. transcode_to_scrubbed_utf8(str)
  114. rescue Encoding::UndefinedConversionError, ArgumentError, Encoding::ConverterNotFoundError
  115. warn "Encoding conversion failed #{$!}"
  116. str.dup.force_encoding(Encoding::UTF_8)
  117. end
  118. 1 def Ruby19.q_value_encode(str, encoding = nil)
  119. encoding = str.encoding.to_s
  120. [Encodings::QuotedPrintable.encode(str), encoding]
  121. end
  122. 1 def Ruby19.q_value_decode(str)
  123. match = str.match(/\=\?(.+)?\?[Qq]\?(.*)\?\=/m)
  124. if match
  125. charset = match[1]
  126. string = match[2].gsub(/_/, '=20')
  127. # Remove trailing = if it exists in a Q encoding
  128. string = string.sub(/\=$/, '')
  129. str = Encodings::QuotedPrintable.decode(string)
  130. str = charset_encoder.encode(str, charset)
  131. # We assume that binary strings hold utf-8 directly to work around
  132. # jruby/jruby#829 which subtly changes String#encode semantics.
  133. str.force_encoding(Encoding::UTF_8) if str.encoding == Encoding::ASCII_8BIT
  134. end
  135. transcode_to_scrubbed_utf8(str)
  136. rescue Encoding::UndefinedConversionError, ArgumentError, Encoding::ConverterNotFoundError
  137. warn "Encoding conversion failed #{$!}"
  138. str.dup.force_encoding(Encoding::UTF_8)
  139. end
  140. 1 def Ruby19.param_decode(str, encoding)
  141. str = uri_parser.unescape(str)
  142. str = charset_encoder.encode(str, encoding) if encoding
  143. transcode_to_scrubbed_utf8(str)
  144. rescue Encoding::UndefinedConversionError, ArgumentError, Encoding::ConverterNotFoundError
  145. warn "Encoding conversion failed #{$!}"
  146. str.dup.force_encoding(Encoding::UTF_8)
  147. end
  148. 1 def Ruby19.param_encode(str)
  149. encoding = str.encoding.to_s.downcase
  150. language = Configuration.instance.param_encode_language
  151. "#{encoding}'#{language}'#{uri_parser.escape(str)}"
  152. end
  153. 1 def Ruby19.uri_parser
  154. @uri_parser ||= URI::Parser.new
  155. end
  156. # Pick a Ruby encoding corresponding to the message charset. Most
  157. # charsets have a Ruby encoding, but some need manual aliasing here.
  158. #
  159. # TODO: add this as a test somewhere:
  160. # Encoding.list.map { |e| [e.to_s.upcase == pick_encoding(e.to_s.downcase.gsub("-", "")), e.to_s] }.select {|a,b| !b}
  161. # Encoding.list.map { |e| [e.to_s == pick_encoding(e.to_s), e.to_s] }.select {|a,b| !b}
  162. 1 def Ruby19.pick_encoding(charset)
  163. charset = charset.to_s
  164. encoding = case charset.downcase
  165. # ISO-8859-8-I etc. http://en.wikipedia.org/wiki/ISO-8859-8-I
  166. when /^iso[-_]?8859-(\d+)(-i)?$/
  167. "ISO-8859-#{$1}"
  168. # ISO-8859-15, ISO-2022-JP and alike
  169. when /^iso[-_]?(\d{4})-?(\w{1,2})$/
  170. "ISO-#{$1}-#{$2}"
  171. # "ISO-2022-JP-KDDI" and alike
  172. when /^iso[-_]?(\d{4})-?(\w{1,2})-?(\w*)$/
  173. "ISO-#{$1}-#{$2}-#{$3}"
  174. # UTF-8, UTF-32BE and alike
  175. when /^utf[\-_]?(\d{1,2})?(\w{1,2})$/
  176. "UTF-#{$1}#{$2}".gsub(/\A(UTF-(?:16|32))\z/, '\\1BE')
  177. # Windows-1252 and alike
  178. when /^windows-?(.*)$/
  179. "Windows-#{$1}"
  180. when '8bit'
  181. Encoding::ASCII_8BIT
  182. # alternatives/misspellings of us-ascii seen in the wild
  183. when /^iso[-_]?646(-us)?$/, 'us=ascii'
  184. Encoding::ASCII
  185. # Microsoft-specific alias for MACROMAN
  186. when 'macintosh'
  187. Encoding::MACROMAN
  188. # Microsoft-specific alias for CP949 (Korean)
  189. when 'ks_c_5601-1987'
  190. Encoding::CP949
  191. # Wrongly written Shift_JIS (Japanese)
  192. when 'shift-jis'
  193. Encoding::Shift_JIS
  194. # GB2312 (Chinese charset) is a subset of GB18030 (its replacement)
  195. when 'gb2312'
  196. Encoding::GB18030
  197. when 'cp-850'
  198. Encoding::CP850
  199. when 'latin2'
  200. Encoding::ISO_8859_2
  201. else
  202. charset
  203. end
  204. convert_to_encoding(encoding)
  205. end
  206. 1 if "string".respond_to?(:byteslice)
  207. 1 def Ruby19.string_byteslice(str, *args)
  208. str.byteslice(*args)
  209. end
  210. else
  211. def Ruby19.string_byteslice(str, *args)
  212. str.unpack('C*').slice(*args).pack('C*').force_encoding(str.encoding)
  213. end
  214. end
  215. 1 class << self
  216. 1 private
  217. 1 def convert_to_encoding(encoding)
  218. if encoding.is_a?(Encoding)
  219. encoding
  220. else
  221. # Fall back to ASCII for charsets that Ruby doesn't recognize
  222. begin
  223. Encoding.find(encoding)
  224. rescue ArgumentError
  225. Encoding::BINARY
  226. end
  227. end
  228. end
  229. 1 def transcode_to_scrubbed_utf8(str)
  230. decoded = str.encode(Encoding::UTF_8, :undef => :replace, :invalid => :replace, :replace => "���")
  231. decoded.valid_encoding? ? decoded : decoded.encode(Encoding::UTF_16LE, :invalid => :replace, :replace => "���").encode(Encoding::UTF_8)
  232. end
  233. end
  234. end
  235. end

target/rubygems/gems/mini_mime-1.0.2/lib/mini_mime.rb

37.5% lines covered

96 relevant lines. 36 lines covered and 60 lines missed.
    
  1. 1 require "mini_mime/version"
  2. 1 require "thread"
  3. 1 module MiniMime
  4. 1 def self.lookup_by_filename(filename)
  5. Db.lookup_by_filename(filename)
  6. end
  7. 1 def self.lookup_by_extension(extension)
  8. Db.lookup_by_extension(extension)
  9. end
  10. 1 def self.lookup_by_content_type(mime)
  11. Db.lookup_by_content_type(mime)
  12. end
  13. 1 module Configuration
  14. 1 class << self
  15. 1 attr_accessor :ext_db_path
  16. 1 attr_accessor :content_type_db_path
  17. end
  18. 1 self.ext_db_path = File.expand_path("../db/ext_mime.db", __FILE__)
  19. 1 self.content_type_db_path = File.expand_path("../db/content_type_mime.db", __FILE__)
  20. end
  21. 1 class Info
  22. 1 BINARY_ENCODINGS = %w(base64 8bit)
  23. 1 attr_accessor :extension, :content_type, :encoding
  24. 1 def initialize(buffer)
  25. @extension, @content_type, @encoding = buffer.split(/\s+/).map!(&:freeze)
  26. end
  27. 1 def [](idx)
  28. if idx == 0
  29. @extension
  30. elsif idx == 1
  31. @content_type
  32. elsif idx == 2
  33. @encoding
  34. end
  35. end
  36. 1 def binary?
  37. BINARY_ENCODINGS.include?(encoding)
  38. end
  39. end
  40. 1 class Db
  41. 1 LOCK = Mutex.new
  42. 1 def self.lookup_by_filename(filename)
  43. extension = File.extname(filename)
  44. return if extension.empty?
  45. extension = extension[1..-1]
  46. extension.downcase!
  47. lookup_by_extension(extension)
  48. end
  49. 1 def self.lookup_by_extension(extension)
  50. LOCK.synchronize do
  51. @db ||= new
  52. @db.lookup_by_extension(extension)
  53. end
  54. end
  55. 1 def self.lookup_by_content_type(content_type)
  56. LOCK.synchronize do
  57. @db ||= new
  58. @db.lookup_by_content_type(content_type)
  59. end
  60. end
  61. 1 class Cache
  62. 1 def initialize(size)
  63. @size = size
  64. @hash = {}
  65. end
  66. 1 def []=(key, val)
  67. rval = @hash[key] = val
  68. @hash.shift if @hash.length > @size
  69. rval
  70. end
  71. 1 def fetch(key, &blk)
  72. @hash.fetch(key, &blk)
  73. end
  74. end
  75. 1 class RandomAccessDb
  76. 1 MAX_CACHED = 100
  77. 1 def initialize(path, sort_order)
  78. @path = path
  79. @file = File.open(@path)
  80. @row_length = @file.readline.length
  81. @file_length = File.size(@path)
  82. @rows = @file_length / @row_length
  83. @hit_cache = Cache.new(MAX_CACHED)
  84. @miss_cache = Cache.new(MAX_CACHED)
  85. @sort_order = sort_order
  86. end
  87. 1 def lookup(val)
  88. @hit_cache.fetch(val) do
  89. @miss_cache.fetch(val) do
  90. data = lookup_uncached(val)
  91. if data
  92. @hit_cache[val] = data
  93. else
  94. @miss_cache[val] = nil
  95. end
  96. data
  97. end
  98. end
  99. end
  100. # lifted from marcandre/backports
  101. 1 def lookup_uncached(val)
  102. from = 0
  103. to = @rows - 1
  104. result = nil
  105. while from <= to do
  106. midpoint = from + (to-from).div(2)
  107. current = resolve(midpoint)
  108. data = current[@sort_order]
  109. if data > val
  110. to = midpoint - 1
  111. elsif data < val
  112. from = midpoint + 1
  113. else
  114. result = current
  115. break
  116. end
  117. end
  118. result
  119. end
  120. 1 def resolve(row)
  121. @file.seek(row*@row_length)
  122. Info.new(@file.readline)
  123. end
  124. end
  125. 1 def initialize
  126. @ext_db = RandomAccessDb.new(Configuration.ext_db_path, 0)
  127. @content_type_db = RandomAccessDb.new(Configuration.content_type_db_path, 1)
  128. end
  129. 1 def lookup_by_extension(extension)
  130. @ext_db.lookup(extension)
  131. end
  132. 1 def lookup_by_content_type(content_type)
  133. @content_type_db.lookup(content_type)
  134. end
  135. end
  136. end

target/rubygems/gems/mini_mime-1.0.2/lib/mini_mime/version.rb

100.0% lines covered

2 relevant lines. 2 lines covered and 0 lines missed.
    
  1. 1 module MiniMime
  2. 1 VERSION = "1.0.2"
  3. end

target/rubygems/gems/minitest-5.11.3/lib/minitest/pride_plugin.rb

37.93% lines covered

58 relevant lines. 22 lines covered and 36 lines missed.
    
  1. 1 require "minitest"
  2. 1 module Minitest
  3. 1 def self.plugin_pride_options opts, _options # :nodoc:
  4. 1 opts.on "-p", "--pride", "Pride. Show your testing pride!" do
  5. PrideIO.pride!
  6. end
  7. end
  8. 1 def self.plugin_pride_init options # :nodoc:
  9. 1 if PrideIO.pride? then
  10. klass = ENV["TERM"] =~ /^xterm|-256color$/ ? PrideLOL : PrideIO
  11. io = klass.new options[:io]
  12. self.reporter.reporters.grep(Minitest::Reporter).each do |rep|
  13. rep.io = io if rep.io.tty?
  14. end
  15. end
  16. end
  17. ##
  18. # Show your testing pride!
  19. 1 class PrideIO
  20. ##
  21. # Activate the pride plugin. Called from both -p option and minitest/pride
  22. 1 def self.pride!
  23. @pride = true
  24. end
  25. ##
  26. # Are we showing our testing pride?
  27. 1 def self.pride?
  28. 1 @pride ||= false
  29. end
  30. # Start an escape sequence
  31. 1 ESC = "\e["
  32. # End the escape sequence
  33. 1 NND = "#{ESC}0m"
  34. # The IO we're going to pipe through.
  35. 1 attr_reader :io
  36. 1 def initialize io # :nodoc:
  37. @io = io
  38. # stolen from /System/Library/Perl/5.10.0/Term/ANSIColor.pm
  39. # also reference http://en.wikipedia.org/wiki/ANSI_escape_code
  40. @colors ||= (31..36).to_a
  41. @size = @colors.size
  42. @index = 0
  43. end
  44. ##
  45. # Wrap print to colorize the output.
  46. 1 def print o
  47. case o
  48. when "." then
  49. io.print pride o
  50. when "E", "F" then
  51. io.print "#{ESC}41m#{ESC}37m#{o}#{NND}"
  52. when "S" then
  53. io.print pride o
  54. else
  55. io.print o
  56. end
  57. end
  58. 1 def puts *o # :nodoc:
  59. o.map! { |s|
  60. s.to_s.sub(/Finished/) {
  61. @index = 0
  62. "Fabulous run".split(//).map { |c|
  63. pride(c)
  64. }.join
  65. }
  66. }
  67. io.puts(*o)
  68. end
  69. ##
  70. # Color a string.
  71. 1 def pride string
  72. string = "*" if string == "."
  73. c = @colors[@index % @size]
  74. @index += 1
  75. "#{ESC}#{c}m#{string}#{NND}"
  76. end
  77. 1 def method_missing msg, *args # :nodoc:
  78. io.send(msg, *args)
  79. end
  80. end
  81. ##
  82. # If you thought the PrideIO was colorful...
  83. #
  84. # (Inspired by lolcat, but with clean math)
  85. 1 class PrideLOL < PrideIO
  86. 1 PI_3 = Math::PI / 3 # :nodoc:
  87. 1 def initialize io # :nodoc:
  88. # walk red, green, and blue around a circle separated by equal thirds.
  89. #
  90. # To visualize, type this into wolfram-alpha:
  91. #
  92. # plot (3*sin(x)+3), (3*sin(x+2*pi/3)+3), (3*sin(x+4*pi/3)+3)
  93. # 6 has wide pretty gradients. 3 == lolcat, about half the width
  94. @colors = (0...(6 * 7)).map { |n|
  95. n *= 1.0 / 6
  96. r = (3 * Math.sin(n ) + 3).to_i
  97. g = (3 * Math.sin(n + 2 * PI_3) + 3).to_i
  98. b = (3 * Math.sin(n + 4 * PI_3) + 3).to_i
  99. # Then we take rgb and encode them in a single number using base 6.
  100. # For some mysterious reason, we add 16... to clear the bottom 4 bits?
  101. # Yes... they're ugly.
  102. 36 * r + 6 * g + b + 16
  103. }
  104. super
  105. end
  106. ##
  107. # Make the string even more colorful. Damnit.
  108. 1 def pride string
  109. c = @colors[@index % @size]
  110. @index += 1
  111. "#{ESC}38;5;#{c}m#{string}#{NND}"
  112. end
  113. end
  114. end

target/rubygems/gems/multi_json-1.13.1/lib/multi_json/adapter.rb

74.07% lines covered

27 relevant lines. 20 lines covered and 7 lines missed.
    
  1. 1 require 'singleton'
  2. 1 require 'multi_json/options'
  3. 1 module MultiJson
  4. 1 class Adapter
  5. 1 extend Options
  6. 1 include Singleton
  7. 1 class << self
  8. 1 def defaults(action, value)
  9. 2 metaclass = class << self; self; end
  10. 1 metaclass.instance_eval do
  11. 1 define_method("default_#{action}_options") { value }
  12. end
  13. end
  14. 1 def load(string, options = {})
  15. string = string.read if string.respond_to?(:read)
  16. fail self::ParseError if blank?(string)
  17. instance.load(string, cached_load_options(options))
  18. end
  19. 1 def dump(object, options = {})
  20. 3 instance.dump(object, cached_dump_options(options))
  21. end
  22. 1 private
  23. 1 def blank?(input)
  24. input.nil? || /\A\s*\z/ === input
  25. rescue ArgumentError # invalid byte sequence in UTF-8
  26. false
  27. end
  28. 1 def cached_dump_options(options)
  29. 3 OptionsCache.fetch(:dump, options) do
  30. 1 dump_options(options).merge(MultiJson.dump_options(options)).merge!(options)
  31. end
  32. end
  33. 1 def cached_load_options(options)
  34. OptionsCache.fetch(:load, options) do
  35. load_options(options).merge(MultiJson.load_options(options)).merge!(options)
  36. end
  37. end
  38. end
  39. end
  40. end

target/rubygems/gems/multi_json-1.13.1/lib/multi_json/adapters/json_common.rb

69.23% lines covered

13 relevant lines. 9 lines covered and 4 lines missed.
    
  1. 1 require 'multi_json/adapter'
  2. 1 module MultiJson
  3. 1 module Adapters
  4. 1 class JsonCommon < Adapter
  5. 1 defaults :load, :create_additions => false, :quirks_mode => true
  6. 1 def load(string, options = {})
  7. if string.respond_to?(:force_encoding)
  8. string = string.dup.force_encoding(::Encoding::ASCII_8BIT)
  9. end
  10. options[:symbolize_names] = true if options.delete(:symbolize_keys)
  11. ::JSON.parse(string, options)
  12. end
  13. 1 def dump(object, options = {})
  14. 3 options.merge!(::JSON::PRETTY_STATE_PROTOTYPE.to_h) if options.delete(:pretty)
  15. 3 object.to_json(options)
  16. end
  17. end
  18. end
  19. end

target/rubygems/gems/multi_json-1.13.1/lib/multi_json/adapters/json_gem.rb

100.0% lines covered

6 relevant lines. 6 lines covered and 0 lines missed.
    
  1. 1 require 'json/ext'
  2. 1 require 'multi_json/adapters/json_common'
  3. 1 module MultiJson
  4. 1 module Adapters
  5. # Use the JSON gem to dump/load.
  6. 1 class JsonGem < JsonCommon
  7. 1 ParseError = ::JSON::ParserError
  8. end
  9. end
  10. end

target/rubygems/gems/rack-2.0.7/lib/rack/conditional_get.rb

54.29% lines covered

35 relevant lines. 19 lines covered and 16 lines missed.
    
  1. 1 require 'rack/utils'
  2. 1 module Rack
  3. # Middleware that enables conditional GET using If-None-Match and
  4. # If-Modified-Since. The application should set either or both of the
  5. # Last-Modified or Etag response headers according to RFC 2616. When
  6. # either of the conditions is met, the response body is set to be zero
  7. # length and the response status is set to 304 Not Modified.
  8. #
  9. # Applications that defer response body generation until the body's each
  10. # message is received will avoid response body generation completely when
  11. # a conditional GET matches.
  12. #
  13. # Adapted from Michael Klishin's Merb implementation:
  14. # https://github.com/wycats/merb/blob/master/merb-core/lib/merb-core/rack/middleware/conditional_get.rb
  15. 1 class ConditionalGet
  16. 1 def initialize(app)
  17. 1 @app = app
  18. end
  19. 1 def call(env)
  20. 2 case env[REQUEST_METHOD]
  21. when "GET", "HEAD"
  22. 2 status, headers, body = @app.call(env)
  23. 2 headers = Utils::HeaderHash.new(headers)
  24. 2 if status == 200 && fresh?(env, headers)
  25. status = 304
  26. headers.delete(CONTENT_TYPE)
  27. headers.delete(CONTENT_LENGTH)
  28. original_body = body
  29. body = Rack::BodyProxy.new([]) do
  30. original_body.close if original_body.respond_to?(:close)
  31. end
  32. end
  33. 2 [status, headers, body]
  34. else
  35. @app.call(env)
  36. end
  37. end
  38. 1 private
  39. 1 def fresh?(env, headers)
  40. 1 modified_since = env['HTTP_IF_MODIFIED_SINCE']
  41. 1 none_match = env['HTTP_IF_NONE_MATCH']
  42. 1 return false unless modified_since || none_match
  43. success = true
  44. success &&= modified_since?(to_rfc2822(modified_since), headers) if modified_since
  45. success &&= etag_matches?(none_match, headers) if none_match
  46. success
  47. end
  48. 1 def etag_matches?(none_match, headers)
  49. etag = headers['ETag'] and etag == none_match
  50. end
  51. 1 def modified_since?(modified_since, headers)
  52. last_modified = to_rfc2822(headers['Last-Modified']) and
  53. modified_since and
  54. modified_since >= last_modified
  55. end
  56. 1 def to_rfc2822(since)
  57. # shortest possible valid date is the obsolete: 1 Nov 97 09:55 A
  58. # anything shorter is invalid, this avoids exceptions for common cases
  59. # most common being the empty string
  60. if since && since.length >= 16
  61. # NOTE: there is no trivial way to write this in a non execption way
  62. # _rfc2822 returns a hash but is not that usable
  63. Time.rfc2822(since) rescue nil
  64. else
  65. nil
  66. end
  67. end
  68. end
  69. end

target/rubygems/gems/rack-2.0.7/lib/rack/etag.rb

97.37% lines covered

38 relevant lines. 37 lines covered and 1 lines missed.
    
  1. 1 require 'rack'
  2. 1 require 'digest/sha2'
  3. 1 module Rack
  4. # Automatically sets the ETag header on all String bodies.
  5. #
  6. # The ETag header is skipped if ETag or Last-Modified headers are sent or if
  7. # a sendfile body (body.responds_to :to_path) is given (since such cases
  8. # should be handled by apache/nginx).
  9. #
  10. # On initialization, you can pass two parameters: a Cache-Control directive
  11. # used when Etag is absent and a directive when it is present. The first
  12. # defaults to nil, while the second defaults to "max-age=0, private, must-revalidate"
  13. 1 class ETag
  14. 1 ETAG_STRING = Rack::ETAG
  15. 1 DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate".freeze
  16. 1 def initialize(app, no_cache_control = nil, cache_control = DEFAULT_CACHE_CONTROL)
  17. 1 @app = app
  18. 1 @cache_control = cache_control
  19. 1 @no_cache_control = no_cache_control
  20. end
  21. 1 def call(env)
  22. 2 status, headers, body = @app.call(env)
  23. 2 if etag_status?(status) && etag_body?(body) && !skip_caching?(headers)
  24. 1 original_body = body
  25. 1 digest, new_body = digest_body(body)
  26. 1 body = Rack::BodyProxy.new(new_body) do
  27. 1 original_body.close if original_body.respond_to?(:close)
  28. end
  29. 1 headers[ETAG_STRING] = %(W/"#{digest}") if digest
  30. end
  31. 2 unless headers[CACHE_CONTROL]
  32. 2 if digest
  33. 1 headers[CACHE_CONTROL] = @cache_control if @cache_control
  34. else
  35. 1 headers[CACHE_CONTROL] = @no_cache_control if @no_cache_control
  36. end
  37. end
  38. 2 [status, headers, body]
  39. end
  40. 1 private
  41. 1 def etag_status?(status)
  42. 2 status == 200 || status == 201
  43. end
  44. 1 def etag_body?(body)
  45. 1 !body.respond_to?(:to_path)
  46. end
  47. 1 def skip_caching?(headers)
  48. 1 (headers[CACHE_CONTROL] && headers[CACHE_CONTROL].include?('no-cache')) ||
  49. headers.key?(ETAG_STRING) || headers.key?('Last-Modified')
  50. end
  51. 1 def digest_body(body)
  52. 1 parts = []
  53. 1 digest = nil
  54. 1 body.each do |part|
  55. 1 parts << part
  56. 1 (digest ||= Digest::SHA256.new) << part unless part.empty?
  57. end
  58. 1 [digest && digest.hexdigest.byteslice(0, 32), parts]
  59. end
  60. end
  61. end

target/rubygems/gems/rack-2.0.7/lib/rack/file.rb

33.71% lines covered

89 relevant lines. 30 lines covered and 59 lines missed.
    
  1. 1 require 'time'
  2. 1 require 'rack/utils'
  3. 1 require 'rack/mime'
  4. 1 require 'rack/request'
  5. 1 require 'rack/head'
  6. 1 module Rack
  7. # Rack::File serves files below the +root+ directory given, according to the
  8. # path info of the Rack request.
  9. # e.g. when Rack::File.new("/etc") is used, you can access 'passwd' file
  10. # as http://localhost:9292/passwd
  11. #
  12. # Handlers can detect if bodies are a Rack::File, and use mechanisms
  13. # like sendfile on the +path+.
  14. 1 class File
  15. 1 ALLOWED_VERBS = %w[GET HEAD OPTIONS]
  16. 1 ALLOW_HEADER = ALLOWED_VERBS.join(', ')
  17. 1 attr_reader :root
  18. 1 def initialize(root, headers={}, default_mime = 'text/plain')
  19. 1 @root = root
  20. 1 @headers = headers
  21. 1 @default_mime = default_mime
  22. 1 @head = Rack::Head.new(lambda { |env| get env })
  23. end
  24. 1 def call(env)
  25. # HEAD requests drop the response body, including 4xx error messages.
  26. @head.call env
  27. end
  28. 1 def get(env)
  29. request = Rack::Request.new env
  30. unless ALLOWED_VERBS.include? request.request_method
  31. return fail(405, "Method Not Allowed", {'Allow' => ALLOW_HEADER})
  32. end
  33. path_info = Utils.unescape_path request.path_info
  34. return fail(400, "Bad Request") unless Utils.valid_path?(path_info)
  35. clean_path_info = Utils.clean_path_info(path_info)
  36. path = ::File.join(@root, clean_path_info)
  37. available = begin
  38. ::File.file?(path) && ::File.readable?(path)
  39. rescue SystemCallError
  40. false
  41. end
  42. if available
  43. serving(request, path)
  44. else
  45. fail(404, "File not found: #{path_info}")
  46. end
  47. end
  48. 1 def serving(request, path)
  49. if request.options?
  50. return [200, {'Allow' => ALLOW_HEADER, CONTENT_LENGTH => '0'}, []]
  51. end
  52. last_modified = ::File.mtime(path).httpdate
  53. return [304, {}, []] if request.get_header('HTTP_IF_MODIFIED_SINCE') == last_modified
  54. headers = { "Last-Modified" => last_modified }
  55. mime_type = mime_type path, @default_mime
  56. headers[CONTENT_TYPE] = mime_type if mime_type
  57. # Set custom headers
  58. @headers.each { |field, content| headers[field] = content } if @headers
  59. response = [ 200, headers ]
  60. size = filesize path
  61. range = nil
  62. ranges = Rack::Utils.get_byte_ranges(request.get_header('HTTP_RANGE'), size)
  63. if ranges.nil? || ranges.length > 1
  64. # No ranges, or multiple ranges (which we don't support):
  65. # TODO: Support multiple byte-ranges
  66. response[0] = 200
  67. range = 0..size-1
  68. elsif ranges.empty?
  69. # Unsatisfiable. Return error, and file size:
  70. response = fail(416, "Byte range unsatisfiable")
  71. response[1]["Content-Range"] = "bytes */#{size}"
  72. return response
  73. else
  74. # Partial content:
  75. range = ranges[0]
  76. response[0] = 206
  77. response[1]["Content-Range"] = "bytes #{range.begin}-#{range.end}/#{size}"
  78. size = range.end - range.begin + 1
  79. end
  80. response[2] = [response_body] unless response_body.nil?
  81. response[1][CONTENT_LENGTH] = size.to_s
  82. response[2] = make_body request, path, range
  83. response
  84. end
  85. 1 class Iterator
  86. 1 attr_reader :path, :range
  87. 1 alias :to_path :path
  88. 1 def initialize path, range
  89. @path = path
  90. @range = range
  91. end
  92. 1 def each
  93. ::File.open(path, "rb") do |file|
  94. file.seek(range.begin)
  95. remaining_len = range.end-range.begin+1
  96. while remaining_len > 0
  97. part = file.read([8192, remaining_len].min)
  98. break unless part
  99. remaining_len -= part.length
  100. yield part
  101. end
  102. end
  103. end
  104. 1 def close; end
  105. end
  106. 1 private
  107. 1 def make_body request, path, range
  108. if request.head?
  109. []
  110. else
  111. Iterator.new path, range
  112. end
  113. end
  114. 1 def fail(status, body, headers = {})
  115. body += "\n"
  116. [
  117. status,
  118. {
  119. CONTENT_TYPE => "text/plain",
  120. CONTENT_LENGTH => body.size.to_s,
  121. "X-Cascade" => "pass"
  122. }.merge!(headers),
  123. [body]
  124. ]
  125. end
  126. # The MIME type for the contents of the file located at @path
  127. 1 def mime_type path, default_mime
  128. Mime.mime_type(::File.extname(path), default_mime)
  129. end
  130. 1 def filesize path
  131. # If response_body is present, use its size.
  132. return response_body.bytesize if response_body
  133. # We check via File::size? whether this file provides size info
  134. # via stat (e.g. /proc files often don't), otherwise we have to
  135. # figure it out by reading the whole file into memory.
  136. ::File.size?(path) || ::File.read(path).bytesize
  137. end
  138. # By default, the response body for file requests is nil.
  139. # In this case, the response body will be generated later
  140. # from the file at @path
  141. 1 def response_body
  142. nil
  143. end
  144. end
  145. end

target/rubygems/gems/rack-2.0.7/lib/rack/head.rb

81.82% lines covered

11 relevant lines. 9 lines covered and 2 lines missed.
    
  1. 1 require 'rack/body_proxy'
  2. 1 module Rack
  3. # Rack::Head returns an empty body for all HEAD requests. It leaves
  4. # all other requests unchanged.
  5. 1 class Head
  6. 1 def initialize(app)
  7. 2 @app = app
  8. end
  9. 1 def call(env)
  10. 2 status, headers, body = @app.call(env)
  11. 2 if env[REQUEST_METHOD] == HEAD
  12. [
  13. status, headers, Rack::BodyProxy.new([]) do
  14. body.close if body.respond_to? :close
  15. end
  16. ]
  17. else
  18. 2 [status, headers, body]
  19. end
  20. end
  21. end
  22. end

target/rubygems/gems/rack-2.0.7/lib/rack/lint.rb

18.41% lines covered

239 relevant lines. 44 lines covered and 195 lines missed.
    
  1. 1 require 'rack/utils'
  2. 1 require 'forwardable'
  3. 1 module Rack
  4. # Rack::Lint validates your application and the requests and
  5. # responses according to the Rack spec.
  6. 1 class Lint
  7. 1 def initialize(app)
  8. @app = app
  9. @content_length = nil
  10. end
  11. # :stopdoc:
  12. 1 class LintError < RuntimeError; end
  13. 1 module Assertion
  14. 1 def assert(message)
  15. unless yield
  16. raise LintError, message
  17. end
  18. end
  19. end
  20. 1 include Assertion
  21. ## This specification aims to formalize the Rack protocol. You
  22. ## can (and should) use Rack::Lint to enforce it.
  23. ##
  24. ## When you develop middleware, be sure to add a Lint before and
  25. ## after to catch all mistakes.
  26. ## = Rack applications
  27. ## A Rack application is a Ruby object (not a class) that
  28. ## responds to +call+.
  29. 1 def call(env=nil)
  30. dup._call(env)
  31. end
  32. 1 def _call(env)
  33. ## It takes exactly one argument, the *environment*
  34. assert("No env given") { env }
  35. check_env env
  36. env[RACK_INPUT] = InputWrapper.new(env[RACK_INPUT])
  37. env[RACK_ERRORS] = ErrorWrapper.new(env[RACK_ERRORS])
  38. ## and returns an Array of exactly three values:
  39. status, headers, @body = @app.call(env)
  40. ## The *status*,
  41. check_status status
  42. ## the *headers*,
  43. check_headers headers
  44. check_hijack_response headers, env
  45. ## and the *body*.
  46. check_content_type status, headers
  47. check_content_length status, headers
  48. @head_request = env[REQUEST_METHOD] == HEAD
  49. [status, headers, self]
  50. end
  51. ## == The Environment
  52. 1 def check_env(env)
  53. ## The environment must be an instance of Hash that includes
  54. ## CGI-like headers. The application is free to modify the
  55. ## environment.
  56. assert("env #{env.inspect} is not a Hash, but #{env.class}") {
  57. env.kind_of? Hash
  58. }
  59. ##
  60. ## The environment is required to include these variables
  61. ## (adopted from PEP333), except when they'd be empty, but see
  62. ## below.
  63. ## <tt>REQUEST_METHOD</tt>:: The HTTP request method, such as
  64. ## "GET" or "POST". This cannot ever
  65. ## be an empty string, and so is
  66. ## always required.
  67. ## <tt>SCRIPT_NAME</tt>:: The initial portion of the request
  68. ## URL's "path" that corresponds to the
  69. ## application object, so that the
  70. ## application knows its virtual
  71. ## "location". This may be an empty
  72. ## string, if the application corresponds
  73. ## to the "root" of the server.
  74. ## <tt>PATH_INFO</tt>:: The remainder of the request URL's
  75. ## "path", designating the virtual
  76. ## "location" of the request's target
  77. ## within the application. This may be an
  78. ## empty string, if the request URL targets
  79. ## the application root and does not have a
  80. ## trailing slash. This value may be
  81. ## percent-encoded when originating from
  82. ## a URL.
  83. ## <tt>QUERY_STRING</tt>:: The portion of the request URL that
  84. ## follows the <tt>?</tt>, if any. May be
  85. ## empty, but is always required!
  86. ## <tt>SERVER_NAME</tt>, <tt>SERVER_PORT</tt>::
  87. ## When combined with <tt>SCRIPT_NAME</tt> and
  88. ## <tt>PATH_INFO</tt>, these variables can be
  89. ## used to complete the URL. Note, however,
  90. ## that <tt>HTTP_HOST</tt>, if present,
  91. ## should be used in preference to
  92. ## <tt>SERVER_NAME</tt> for reconstructing
  93. ## the request URL.
  94. ## <tt>SERVER_NAME</tt> and <tt>SERVER_PORT</tt>
  95. ## can never be empty strings, and so
  96. ## are always required.
  97. ## <tt>HTTP_</tt> Variables:: Variables corresponding to the
  98. ## client-supplied HTTP request
  99. ## headers (i.e., variables whose
  100. ## names begin with <tt>HTTP_</tt>). The
  101. ## presence or absence of these
  102. ## variables should correspond with
  103. ## the presence or absence of the
  104. ## appropriate HTTP header in the
  105. ## request. See
  106. ## <a href="https://tools.ietf.org/html/rfc3875#section-4.1.18">
  107. ## RFC3875 section 4.1.18</a> for
  108. ## specific behavior.
  109. ## In addition to this, the Rack environment must include these
  110. ## Rack-specific variables:
  111. ## <tt>rack.version</tt>:: The Array representing this version of Rack
  112. ## See Rack::VERSION, that corresponds to
  113. ## the version of this SPEC.
  114. ## <tt>rack.url_scheme</tt>:: +http+ or +https+, depending on the
  115. ## request URL.
  116. ## <tt>rack.input</tt>:: See below, the input stream.
  117. ## <tt>rack.errors</tt>:: See below, the error stream.
  118. ## <tt>rack.multithread</tt>:: true if the application object may be
  119. ## simultaneously invoked by another thread
  120. ## in the same process, false otherwise.
  121. ## <tt>rack.multiprocess</tt>:: true if an equivalent application object
  122. ## may be simultaneously invoked by another
  123. ## process, false otherwise.
  124. ## <tt>rack.run_once</tt>:: true if the server expects
  125. ## (but does not guarantee!) that the
  126. ## application will only be invoked this one
  127. ## time during the life of its containing
  128. ## process. Normally, this will only be true
  129. ## for a server based on CGI
  130. ## (or something similar).
  131. ## <tt>rack.hijack?</tt>:: present and true if the server supports
  132. ## connection hijacking. See below, hijacking.
  133. ## <tt>rack.hijack</tt>:: an object responding to #call that must be
  134. ## called at least once before using
  135. ## rack.hijack_io.
  136. ## It is recommended #call return rack.hijack_io
  137. ## as well as setting it in env if necessary.
  138. ## <tt>rack.hijack_io</tt>:: if rack.hijack? is true, and rack.hijack
  139. ## has received #call, this will contain
  140. ## an object resembling an IO. See hijacking.
  141. ## Additional environment specifications have approved to
  142. ## standardized middleware APIs. None of these are required to
  143. ## be implemented by the server.
  144. ## <tt>rack.session</tt>:: A hash like interface for storing
  145. ## request session data.
  146. ## The store must implement:
  147. if session = env[RACK_SESSION]
  148. ## store(key, value) (aliased as []=);
  149. assert("session #{session.inspect} must respond to store and []=") {
  150. session.respond_to?(:store) && session.respond_to?(:[]=)
  151. }
  152. ## fetch(key, default = nil) (aliased as []);
  153. assert("session #{session.inspect} must respond to fetch and []") {
  154. session.respond_to?(:fetch) && session.respond_to?(:[])
  155. }
  156. ## delete(key);
  157. assert("session #{session.inspect} must respond to delete") {
  158. session.respond_to?(:delete)
  159. }
  160. ## clear;
  161. assert("session #{session.inspect} must respond to clear") {
  162. session.respond_to?(:clear)
  163. }
  164. end
  165. ## <tt>rack.logger</tt>:: A common object interface for logging messages.
  166. ## The object must implement:
  167. if logger = env[RACK_LOGGER]
  168. ## info(message, &block)
  169. assert("logger #{logger.inspect} must respond to info") {
  170. logger.respond_to?(:info)
  171. }
  172. ## debug(message, &block)
  173. assert("logger #{logger.inspect} must respond to debug") {
  174. logger.respond_to?(:debug)
  175. }
  176. ## warn(message, &block)
  177. assert("logger #{logger.inspect} must respond to warn") {
  178. logger.respond_to?(:warn)
  179. }
  180. ## error(message, &block)
  181. assert("logger #{logger.inspect} must respond to error") {
  182. logger.respond_to?(:error)
  183. }
  184. ## fatal(message, &block)
  185. assert("logger #{logger.inspect} must respond to fatal") {
  186. logger.respond_to?(:fatal)
  187. }
  188. end
  189. ## <tt>rack.multipart.buffer_size</tt>:: An Integer hint to the multipart parser as to what chunk size to use for reads and writes.
  190. if bufsize = env[RACK_MULTIPART_BUFFER_SIZE]
  191. assert("rack.multipart.buffer_size must be an Integer > 0 if specified") {
  192. bufsize.is_a?(Integer) && bufsize > 0
  193. }
  194. end
  195. ## <tt>rack.multipart.tempfile_factory</tt>:: An object responding to #call with two arguments, the filename and content_type given for the multipart form field, and returning an IO-like object that responds to #<< and optionally #rewind. This factory will be used to instantiate the tempfile for each multipart form file upload field, rather than the default class of Tempfile.
  196. if tempfile_factory = env[RACK_MULTIPART_TEMPFILE_FACTORY]
  197. assert("rack.multipart.tempfile_factory must respond to #call") { tempfile_factory.respond_to?(:call) }
  198. env[RACK_MULTIPART_TEMPFILE_FACTORY] = lambda do |filename, content_type|
  199. io = tempfile_factory.call(filename, content_type)
  200. assert("rack.multipart.tempfile_factory return value must respond to #<<") { io.respond_to?(:<<) }
  201. io
  202. end
  203. end
  204. ## The server or the application can store their own data in the
  205. ## environment, too. The keys must contain at least one dot,
  206. ## and should be prefixed uniquely. The prefix <tt>rack.</tt>
  207. ## is reserved for use with the Rack core distribution and other
  208. ## accepted specifications and must not be used otherwise.
  209. ##
  210. %w[REQUEST_METHOD SERVER_NAME SERVER_PORT
  211. QUERY_STRING
  212. rack.version rack.input rack.errors
  213. rack.multithread rack.multiprocess rack.run_once].each { |header|
  214. assert("env missing required key #{header}") { env.include? header }
  215. }
  216. ## The environment must not contain the keys
  217. ## <tt>HTTP_CONTENT_TYPE</tt> or <tt>HTTP_CONTENT_LENGTH</tt>
  218. ## (use the versions without <tt>HTTP_</tt>).
  219. %w[HTTP_CONTENT_TYPE HTTP_CONTENT_LENGTH].each { |header|
  220. assert("env contains #{header}, must use #{header[5,-1]}") {
  221. not env.include? header
  222. }
  223. }
  224. ## The CGI keys (named without a period) must have String values.
  225. env.each { |key, value|
  226. next if key.include? "." # Skip extensions
  227. assert("env variable #{key} has non-string value #{value.inspect}") {
  228. value.kind_of? String
  229. }
  230. }
  231. ## There are the following restrictions:
  232. ## * <tt>rack.version</tt> must be an array of Integers.
  233. assert("rack.version must be an Array, was #{env[RACK_VERSION].class}") {
  234. env[RACK_VERSION].kind_of? Array
  235. }
  236. ## * <tt>rack.url_scheme</tt> must either be +http+ or +https+.
  237. assert("rack.url_scheme unknown: #{env[RACK_URL_SCHEME].inspect}") {
  238. %w[http https].include?(env[RACK_URL_SCHEME])
  239. }
  240. ## * There must be a valid input stream in <tt>rack.input</tt>.
  241. check_input env[RACK_INPUT]
  242. ## * There must be a valid error stream in <tt>rack.errors</tt>.
  243. check_error env[RACK_ERRORS]
  244. ## * There may be a valid hijack stream in <tt>rack.hijack_io</tt>
  245. check_hijack env
  246. ## * The <tt>REQUEST_METHOD</tt> must be a valid token.
  247. assert("REQUEST_METHOD unknown: #{env[REQUEST_METHOD]}") {
  248. env[REQUEST_METHOD] =~ /\A[0-9A-Za-z!\#$%&'*+.^_`|~-]+\z/
  249. }
  250. ## * The <tt>SCRIPT_NAME</tt>, if non-empty, must start with <tt>/</tt>
  251. assert("SCRIPT_NAME must start with /") {
  252. !env.include?(SCRIPT_NAME) ||
  253. env[SCRIPT_NAME] == "" ||
  254. env[SCRIPT_NAME] =~ /\A\//
  255. }
  256. ## * The <tt>PATH_INFO</tt>, if non-empty, must start with <tt>/</tt>
  257. assert("PATH_INFO must start with /") {
  258. !env.include?(PATH_INFO) ||
  259. env[PATH_INFO] == "" ||
  260. env[PATH_INFO] =~ /\A\//
  261. }
  262. ## * The <tt>CONTENT_LENGTH</tt>, if given, must consist of digits only.
  263. assert("Invalid CONTENT_LENGTH: #{env["CONTENT_LENGTH"]}") {
  264. !env.include?("CONTENT_LENGTH") || env["CONTENT_LENGTH"] =~ /\A\d+\z/
  265. }
  266. ## * One of <tt>SCRIPT_NAME</tt> or <tt>PATH_INFO</tt> must be
  267. ## set. <tt>PATH_INFO</tt> should be <tt>/</tt> if
  268. ## <tt>SCRIPT_NAME</tt> is empty.
  269. assert("One of SCRIPT_NAME or PATH_INFO must be set (make PATH_INFO '/' if SCRIPT_NAME is empty)") {
  270. env[SCRIPT_NAME] || env[PATH_INFO]
  271. }
  272. ## <tt>SCRIPT_NAME</tt> never should be <tt>/</tt>, but instead be empty.
  273. assert("SCRIPT_NAME cannot be '/', make it '' and PATH_INFO '/'") {
  274. env[SCRIPT_NAME] != "/"
  275. }
  276. end
  277. ## === The Input Stream
  278. ##
  279. ## The input stream is an IO-like object which contains the raw HTTP
  280. ## POST data.
  281. 1 def check_input(input)
  282. ## When applicable, its external encoding must be "ASCII-8BIT" and it
  283. ## must be opened in binary mode, for Ruby 1.9 compatibility.
  284. assert("rack.input #{input} does not have ASCII-8BIT as its external encoding") {
  285. input.external_encoding.name == "ASCII-8BIT"
  286. } if input.respond_to?(:external_encoding)
  287. assert("rack.input #{input} is not opened in binary mode") {
  288. input.binmode?
  289. } if input.respond_to?(:binmode?)
  290. ## The input stream must respond to +gets+, +each+, +read+ and +rewind+.
  291. [:gets, :each, :read, :rewind].each { |method|
  292. assert("rack.input #{input} does not respond to ##{method}") {
  293. input.respond_to? method
  294. }
  295. }
  296. end
  297. 1 class InputWrapper
  298. 1 include Assertion
  299. 1 def initialize(input)
  300. @input = input
  301. end
  302. ## * +gets+ must be called without arguments and return a string,
  303. ## or +nil+ on EOF.
  304. 1 def gets(*args)
  305. assert("rack.input#gets called with arguments") { args.size == 0 }
  306. v = @input.gets
  307. assert("rack.input#gets didn't return a String") {
  308. v.nil? or v.kind_of? String
  309. }
  310. v
  311. end
  312. ## * +read+ behaves like IO#read.
  313. ## Its signature is <tt>read([length, [buffer]])</tt>.
  314. ##
  315. ## If given, +length+ must be a non-negative Integer (>= 0) or +nil+,
  316. ## and +buffer+ must be a String and may not be nil.
  317. ##
  318. ## If +length+ is given and not nil, then this method reads at most
  319. ## +length+ bytes from the input stream.
  320. ##
  321. ## If +length+ is not given or nil, then this method reads
  322. ## all data until EOF.
  323. ##
  324. ## When EOF is reached, this method returns nil if +length+ is given
  325. ## and not nil, or "" if +length+ is not given or is nil.
  326. ##
  327. ## If +buffer+ is given, then the read data will be placed
  328. ## into +buffer+ instead of a newly created String object.
  329. 1 def read(*args)
  330. assert("rack.input#read called with too many arguments") {
  331. args.size <= 2
  332. }
  333. if args.size >= 1
  334. assert("rack.input#read called with non-integer and non-nil length") {
  335. args.first.kind_of?(Integer) || args.first.nil?
  336. }
  337. assert("rack.input#read called with a negative length") {
  338. args.first.nil? || args.first >= 0
  339. }
  340. end
  341. if args.size >= 2
  342. assert("rack.input#read called with non-String buffer") {
  343. args[1].kind_of?(String)
  344. }
  345. end
  346. v = @input.read(*args)
  347. assert("rack.input#read didn't return nil or a String") {
  348. v.nil? or v.kind_of? String
  349. }
  350. if args[0].nil?
  351. assert("rack.input#read(nil) returned nil on EOF") {
  352. !v.nil?
  353. }
  354. end
  355. v
  356. end
  357. ## * +each+ must be called without arguments and only yield Strings.
  358. 1 def each(*args)
  359. assert("rack.input#each called with arguments") { args.size == 0 }
  360. @input.each { |line|
  361. assert("rack.input#each didn't yield a String") {
  362. line.kind_of? String
  363. }
  364. yield line
  365. }
  366. end
  367. ## * +rewind+ must be called without arguments. It rewinds the input
  368. ## stream back to the beginning. It must not raise Errno::ESPIPE:
  369. ## that is, it may not be a pipe or a socket. Therefore, handler
  370. ## developers must buffer the input data into some rewindable object
  371. ## if the underlying input stream is not rewindable.
  372. 1 def rewind(*args)
  373. assert("rack.input#rewind called with arguments") { args.size == 0 }
  374. assert("rack.input#rewind raised Errno::ESPIPE") {
  375. begin
  376. @input.rewind
  377. true
  378. rescue Errno::ESPIPE
  379. false
  380. end
  381. }
  382. end
  383. ## * +close+ must never be called on the input stream.
  384. 1 def close(*args)
  385. assert("rack.input#close must not be called") { false }
  386. end
  387. end
  388. ## === The Error Stream
  389. 1 def check_error(error)
  390. ## The error stream must respond to +puts+, +write+ and +flush+.
  391. [:puts, :write, :flush].each { |method|
  392. assert("rack.error #{error} does not respond to ##{method}") {
  393. error.respond_to? method
  394. }
  395. }
  396. end
  397. 1 class ErrorWrapper
  398. 1 include Assertion
  399. 1 def initialize(error)
  400. @error = error
  401. end
  402. ## * +puts+ must be called with a single argument that responds to +to_s+.
  403. 1 def puts(str)
  404. @error.puts str
  405. end
  406. ## * +write+ must be called with a single argument that is a String.
  407. 1 def write(str)
  408. assert("rack.errors#write not called with a String") { str.kind_of? String }
  409. @error.write str
  410. end
  411. ## * +flush+ must be called without arguments and must be called
  412. ## in order to make the error appear for sure.
  413. 1 def flush
  414. @error.flush
  415. end
  416. ## * +close+ must never be called on the error stream.
  417. 1 def close(*args)
  418. assert("rack.errors#close must not be called") { false }
  419. end
  420. end
  421. 1 class HijackWrapper
  422. 1 include Assertion
  423. 1 extend Forwardable
  424. 1 REQUIRED_METHODS = [
  425. :read, :write, :read_nonblock, :write_nonblock, :flush, :close,
  426. :close_read, :close_write, :closed?
  427. ]
  428. 1 def_delegators :@io, *REQUIRED_METHODS
  429. 1 def initialize(io)
  430. @io = io
  431. REQUIRED_METHODS.each do |meth|
  432. assert("rack.hijack_io must respond to #{meth}") { io.respond_to? meth }
  433. end
  434. end
  435. end
  436. ## === Hijacking
  437. #
  438. # AUTHORS: n.b. The trailing whitespace between paragraphs is important and
  439. # should not be removed. The whitespace creates paragraphs in the RDoc
  440. # output.
  441. #
  442. ## ==== Request (before status)
  443. 1 def check_hijack(env)
  444. if env[RACK_IS_HIJACK]
  445. ## If rack.hijack? is true then rack.hijack must respond to #call.
  446. original_hijack = env[RACK_HIJACK]
  447. assert("rack.hijack must respond to call") { original_hijack.respond_to?(:call) }
  448. env[RACK_HIJACK] = proc do
  449. ## rack.hijack must return the io that will also be assigned (or is
  450. ## already present, in rack.hijack_io.
  451. io = original_hijack.call
  452. HijackWrapper.new(io)
  453. ##
  454. ## rack.hijack_io must respond to:
  455. ## <tt>read, write, read_nonblock, write_nonblock, flush, close,
  456. ## close_read, close_write, closed?</tt>
  457. ##
  458. ## The semantics of these IO methods must be a best effort match to
  459. ## those of a normal ruby IO or Socket object, using standard
  460. ## arguments and raising standard exceptions. Servers are encouraged
  461. ## to simply pass on real IO objects, although it is recognized that
  462. ## this approach is not directly compatible with SPDY and HTTP 2.0.
  463. ##
  464. ## IO provided in rack.hijack_io should preference the
  465. ## IO::WaitReadable and IO::WaitWritable APIs wherever supported.
  466. ##
  467. ## There is a deliberate lack of full specification around
  468. ## rack.hijack_io, as semantics will change from server to server.
  469. ## Users are encouraged to utilize this API with a knowledge of their
  470. ## server choice, and servers may extend the functionality of
  471. ## hijack_io to provide additional features to users. The purpose of
  472. ## rack.hijack is for Rack to "get out of the way", as such, Rack only
  473. ## provides the minimum of specification and support.
  474. env[RACK_HIJACK_IO] = HijackWrapper.new(env[RACK_HIJACK_IO])
  475. io
  476. end
  477. else
  478. ##
  479. ## If rack.hijack? is false, then rack.hijack should not be set.
  480. assert("rack.hijack? is false, but rack.hijack is present") { env[RACK_HIJACK].nil? }
  481. ##
  482. ## If rack.hijack? is false, then rack.hijack_io should not be set.
  483. assert("rack.hijack? is false, but rack.hijack_io is present") { env[RACK_HIJACK_IO].nil? }
  484. end
  485. end
  486. ## ==== Response (after headers)
  487. ## It is also possible to hijack a response after the status and headers
  488. ## have been sent.
  489. 1 def check_hijack_response(headers, env)
  490. # this check uses headers like a hash, but the spec only requires
  491. # headers respond to #each
  492. headers = Rack::Utils::HeaderHash.new(headers)
  493. ## In order to do this, an application may set the special header
  494. ## <tt>rack.hijack</tt> to an object that responds to <tt>call</tt>
  495. ## accepting an argument that conforms to the <tt>rack.hijack_io</tt>
  496. ## protocol.
  497. ##
  498. ## After the headers have been sent, and this hijack callback has been
  499. ## called, the application is now responsible for the remaining lifecycle
  500. ## of the IO. The application is also responsible for maintaining HTTP
  501. ## semantics. Of specific note, in almost all cases in the current SPEC,
  502. ## applications will have wanted to specify the header Connection:close in
  503. ## HTTP/1.1, and not Connection:keep-alive, as there is no protocol for
  504. ## returning hijacked sockets to the web server. For that purpose, use the
  505. ## body streaming API instead (progressively yielding strings via each).
  506. ##
  507. ## Servers must ignore the <tt>body</tt> part of the response tuple when
  508. ## the <tt>rack.hijack</tt> response API is in use.
  509. if env[RACK_IS_HIJACK] && headers[RACK_HIJACK]
  510. assert('rack.hijack header must respond to #call') {
  511. headers[RACK_HIJACK].respond_to? :call
  512. }
  513. original_hijack = headers[RACK_HIJACK]
  514. headers[RACK_HIJACK] = proc do |io|
  515. original_hijack.call HijackWrapper.new(io)
  516. end
  517. else
  518. ##
  519. ## The special response header <tt>rack.hijack</tt> must only be set
  520. ## if the request env has <tt>rack.hijack?</tt> <tt>true</tt>.
  521. assert('rack.hijack header must not be present if server does not support hijacking') {
  522. headers[RACK_HIJACK].nil?
  523. }
  524. end
  525. end
  526. ## ==== Conventions
  527. ## * Middleware should not use hijack unless it is handling the whole
  528. ## response.
  529. ## * Middleware may wrap the IO object for the response pattern.
  530. ## * Middleware should not wrap the IO object for the request pattern. The
  531. ## request pattern is intended to provide the hijacker with "raw tcp".
  532. ## == The Response
  533. ## === The Status
  534. 1 def check_status(status)
  535. ## This is an HTTP status. When parsed as integer (+to_i+), it must be
  536. ## greater than or equal to 100.
  537. assert("Status must be >=100 seen as integer") { status.to_i >= 100 }
  538. end
  539. ## === The Headers
  540. 1 def check_headers(header)
  541. ## The header must respond to +each+, and yield values of key and value.
  542. assert("headers object should respond to #each, but doesn't (got #{header.class} as headers)") {
  543. header.respond_to? :each
  544. }
  545. header.each { |key, value|
  546. ## Special headers starting "rack." are for communicating with the
  547. ## server, and must not be sent back to the client.
  548. next if key =~ /^rack\..+$/
  549. ## The header keys must be Strings.
  550. assert("header key must be a string, was #{key.class}") {
  551. key.kind_of? String
  552. }
  553. ## The header must not contain a +Status+ key.
  554. assert("header must not contain Status") { key.downcase != "status" }
  555. ## The header must conform to RFC7230 token specification, i.e. cannot
  556. ## contain non-printable ASCII, DQUOTE or "(),/:;<=>?@[\]{}".
  557. assert("invalid header name: #{key}") { key !~ /[\(\),\/:;<=>\?@\[\\\]{}[:cntrl:]]/ }
  558. ## The values of the header must be Strings,
  559. assert("a header value must be a String, but the value of " +
  560. "'#{key}' is a #{value.class}") { value.kind_of? String }
  561. ## consisting of lines (for multiple header values, e.g. multiple
  562. ## <tt>Set-Cookie</tt> values) separated by "\\n".
  563. value.split("\n").each { |item|
  564. ## The lines must not contain characters below 037.
  565. assert("invalid header value #{key}: #{item.inspect}") {
  566. item !~ /[\000-\037]/
  567. }
  568. }
  569. }
  570. end
  571. ## === The Content-Type
  572. 1 def check_content_type(status, headers)
  573. headers.each { |key, value|
  574. ## There must not be a <tt>Content-Type</tt>, when the +Status+ is 1xx,
  575. ## 204 or 304.
  576. if key.downcase == "content-type"
  577. assert("Content-Type header found in #{status} response, not allowed") {
  578. not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i
  579. }
  580. return
  581. end
  582. }
  583. end
  584. ## === The Content-Length
  585. 1 def check_content_length(status, headers)
  586. headers.each { |key, value|
  587. if key.downcase == 'content-length'
  588. ## There must not be a <tt>Content-Length</tt> header when the
  589. ## +Status+ is 1xx, 204 or 304.
  590. assert("Content-Length header found in #{status} response, not allowed") {
  591. not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i
  592. }
  593. @content_length = value
  594. end
  595. }
  596. end
  597. 1 def verify_content_length(bytes)
  598. if @head_request
  599. assert("Response body was given for HEAD request, but should be empty") {
  600. bytes == 0
  601. }
  602. elsif @content_length
  603. assert("Content-Length header was #{@content_length}, but should be #{bytes}") {
  604. @content_length == bytes.to_s
  605. }
  606. end
  607. end
  608. ## === The Body
  609. 1 def each
  610. @closed = false
  611. bytes = 0
  612. ## The Body must respond to +each+
  613. assert("Response body must respond to each") do
  614. @body.respond_to?(:each)
  615. end
  616. @body.each { |part|
  617. ## and must only yield String values.
  618. assert("Body yielded non-string value #{part.inspect}") {
  619. part.kind_of? String
  620. }
  621. bytes += part.bytesize
  622. yield part
  623. }
  624. verify_content_length(bytes)
  625. ##
  626. ## The Body itself should not be an instance of String, as this will
  627. ## break in Ruby 1.9.
  628. ##
  629. ## If the Body responds to +close+, it will be called after iteration. If
  630. ## the body is replaced by a middleware after action, the original body
  631. ## must be closed first, if it responds to close.
  632. # XXX howto: assert("Body has not been closed") { @closed }
  633. ##
  634. ## If the Body responds to +to_path+, it must return a String
  635. ## identifying the location of a file whose contents are identical
  636. ## to that produced by calling +each+; this may be used by the
  637. ## server as an alternative, possibly more efficient way to
  638. ## transport the response.
  639. if @body.respond_to?(:to_path)
  640. assert("The file identified by body.to_path does not exist") {
  641. ::File.exist? @body.to_path
  642. }
  643. end
  644. ##
  645. ## The Body commonly is an Array of Strings, the application
  646. ## instance itself, or a File-like object.
  647. end
  648. 1 def close
  649. @closed = true
  650. @body.close if @body.respond_to?(:close)
  651. end
  652. # :startdoc:
  653. end
  654. end
  655. ## == Thanks
  656. ## Some parts of this specification are adopted from PEP333: Python
  657. ## Web Server Gateway Interface
  658. ## v1.0 (http://www.python.org/dev/peps/pep-0333/). I'd like to thank
  659. ## everyone involved in that effort.

target/rubygems/gems/rack-2.0.7/lib/rack/method_override.rb

57.14% lines covered

28 relevant lines. 16 lines covered and 12 lines missed.
    
  1. 1 module Rack
  2. 1 class MethodOverride
  3. 1 HTTP_METHODS = %w[GET HEAD PUT POST DELETE OPTIONS PATCH LINK UNLINK]
  4. 1 METHOD_OVERRIDE_PARAM_KEY = "_method".freeze
  5. 1 HTTP_METHOD_OVERRIDE_HEADER = "HTTP_X_HTTP_METHOD_OVERRIDE".freeze
  6. 1 ALLOWED_METHODS = %w[POST]
  7. 1 def initialize(app)
  8. 1 @app = app
  9. end
  10. 1 def call(env)
  11. 2 if allowed_methods.include?(env[REQUEST_METHOD])
  12. method = method_override(env)
  13. if HTTP_METHODS.include?(method)
  14. env[RACK_METHODOVERRIDE_ORIGINAL_METHOD] = env[REQUEST_METHOD]
  15. env[REQUEST_METHOD] = method
  16. end
  17. end
  18. 2 @app.call(env)
  19. end
  20. 1 def method_override(env)
  21. req = Request.new(env)
  22. method = method_override_param(req) ||
  23. env[HTTP_METHOD_OVERRIDE_HEADER]
  24. begin
  25. method.to_s.upcase
  26. rescue ArgumentError
  27. env[RACK_ERRORS].puts "Invalid string for method"
  28. end
  29. end
  30. 1 private
  31. 1 def allowed_methods
  32. 2 ALLOWED_METHODS
  33. end
  34. 1 def method_override_param(req)
  35. req.POST[METHOD_OVERRIDE_PARAM_KEY]
  36. rescue Utils::InvalidParameterError, Utils::ParameterTypeError
  37. req.get_header(RACK_ERRORS).puts "Invalid or incomplete POST params"
  38. rescue EOFError
  39. req.get_header(RACK_ERRORS).puts "Bad request content body"
  40. end
  41. end
  42. end

target/rubygems/gems/rack-2.0.7/lib/rack/mime.rb

63.64% lines covered

11 relevant lines. 7 lines covered and 4 lines missed.
    
  1. 1 module Rack
  2. 1 module Mime
  3. # Returns String with mime type if found, otherwise use +fallback+.
  4. # +ext+ should be filename extension in the '.ext' format that
  5. # File.extname(file) returns.
  6. # +fallback+ may be any object
  7. #
  8. # Also see the documentation for MIME_TYPES
  9. #
  10. # Usage:
  11. # Rack::Mime.mime_type('.foo')
  12. #
  13. # This is a shortcut for:
  14. # Rack::Mime::MIME_TYPES.fetch('.foo', 'application/octet-stream')
  15. 1 def mime_type(ext, fallback='application/octet-stream')
  16. MIME_TYPES.fetch(ext.to_s.downcase, fallback)
  17. end
  18. 1 module_function :mime_type
  19. # Returns true if the given value is a mime match for the given mime match
  20. # specification, false otherwise.
  21. #
  22. # Rack::Mime.match?('text/html', 'text/*') => true
  23. # Rack::Mime.match?('text/plain', '*') => true
  24. # Rack::Mime.match?('text/html', 'application/json') => false
  25. 1 def match?(value, matcher)
  26. v1, v2 = value.split('/', 2)
  27. m1, m2 = matcher.split('/', 2)
  28. (m1 == '*' || v1 == m1) && (m2.nil? || m2 == '*' || m2 == v2)
  29. end
  30. 1 module_function :match?
  31. # List of most common mime-types, selected various sources
  32. # according to their usefulness in a webserving scope for Ruby
  33. # users.
  34. #
  35. # To amend this list with your local mime.types list you can use:
  36. #
  37. # require 'webrick/httputils'
  38. # list = WEBrick::HTTPUtils.load_mime_types('/etc/mime.types')
  39. # Rack::Mime::MIME_TYPES.merge!(list)
  40. #
  41. # N.B. On Ubuntu the mime.types file does not include the leading period, so
  42. # users may need to modify the data before merging into the hash.
  43. 1 MIME_TYPES = {
  44. ".123" => "application/vnd.lotus-1-2-3",
  45. ".3dml" => "text/vnd.in3d.3dml",
  46. ".3g2" => "video/3gpp2",
  47. ".3gp" => "video/3gpp",
  48. ".a" => "application/octet-stream",
  49. ".acc" => "application/vnd.americandynamics.acc",
  50. ".ace" => "application/x-ace-compressed",
  51. ".acu" => "application/vnd.acucobol",
  52. ".aep" => "application/vnd.audiograph",
  53. ".afp" => "application/vnd.ibm.modcap",
  54. ".ai" => "application/postscript",
  55. ".aif" => "audio/x-aiff",
  56. ".aiff" => "audio/x-aiff",
  57. ".ami" => "application/vnd.amiga.ami",
  58. ".appcache" => "text/cache-manifest",
  59. ".apr" => "application/vnd.lotus-approach",
  60. ".asc" => "application/pgp-signature",
  61. ".asf" => "video/x-ms-asf",
  62. ".asm" => "text/x-asm",
  63. ".aso" => "application/vnd.accpac.simply.aso",
  64. ".asx" => "video/x-ms-asf",
  65. ".atc" => "application/vnd.acucorp",
  66. ".atom" => "application/atom+xml",
  67. ".atomcat" => "application/atomcat+xml",
  68. ".atomsvc" => "application/atomsvc+xml",
  69. ".atx" => "application/vnd.antix.game-component",
  70. ".au" => "audio/basic",
  71. ".avi" => "video/x-msvideo",
  72. ".bat" => "application/x-msdownload",
  73. ".bcpio" => "application/x-bcpio",
  74. ".bdm" => "application/vnd.syncml.dm+wbxml",
  75. ".bh2" => "application/vnd.fujitsu.oasysprs",
  76. ".bin" => "application/octet-stream",
  77. ".bmi" => "application/vnd.bmi",
  78. ".bmp" => "image/bmp",
  79. ".box" => "application/vnd.previewsystems.box",
  80. ".btif" => "image/prs.btif",
  81. ".bz" => "application/x-bzip",
  82. ".bz2" => "application/x-bzip2",
  83. ".c" => "text/x-c",
  84. ".c4g" => "application/vnd.clonk.c4group",
  85. ".cab" => "application/vnd.ms-cab-compressed",
  86. ".cc" => "text/x-c",
  87. ".ccxml" => "application/ccxml+xml",
  88. ".cdbcmsg" => "application/vnd.contact.cmsg",
  89. ".cdkey" => "application/vnd.mediastation.cdkey",
  90. ".cdx" => "chemical/x-cdx",
  91. ".cdxml" => "application/vnd.chemdraw+xml",
  92. ".cdy" => "application/vnd.cinderella",
  93. ".cer" => "application/pkix-cert",
  94. ".cgm" => "image/cgm",
  95. ".chat" => "application/x-chat",
  96. ".chm" => "application/vnd.ms-htmlhelp",
  97. ".chrt" => "application/vnd.kde.kchart",
  98. ".cif" => "chemical/x-cif",
  99. ".cii" => "application/vnd.anser-web-certificate-issue-initiation",
  100. ".cil" => "application/vnd.ms-artgalry",
  101. ".cla" => "application/vnd.claymore",
  102. ".class" => "application/octet-stream",
  103. ".clkk" => "application/vnd.crick.clicker.keyboard",
  104. ".clkp" => "application/vnd.crick.clicker.palette",
  105. ".clkt" => "application/vnd.crick.clicker.template",
  106. ".clkw" => "application/vnd.crick.clicker.wordbank",
  107. ".clkx" => "application/vnd.crick.clicker",
  108. ".clp" => "application/x-msclip",
  109. ".cmc" => "application/vnd.cosmocaller",
  110. ".cmdf" => "chemical/x-cmdf",
  111. ".cml" => "chemical/x-cml",
  112. ".cmp" => "application/vnd.yellowriver-custom-menu",
  113. ".cmx" => "image/x-cmx",
  114. ".com" => "application/x-msdownload",
  115. ".conf" => "text/plain",
  116. ".cpio" => "application/x-cpio",
  117. ".cpp" => "text/x-c",
  118. ".cpt" => "application/mac-compactpro",
  119. ".crd" => "application/x-mscardfile",
  120. ".crl" => "application/pkix-crl",
  121. ".crt" => "application/x-x509-ca-cert",
  122. ".csh" => "application/x-csh",
  123. ".csml" => "chemical/x-csml",
  124. ".csp" => "application/vnd.commonspace",
  125. ".css" => "text/css",
  126. ".csv" => "text/csv",
  127. ".curl" => "application/vnd.curl",
  128. ".cww" => "application/prs.cww",
  129. ".cxx" => "text/x-c",
  130. ".daf" => "application/vnd.mobius.daf",
  131. ".davmount" => "application/davmount+xml",
  132. ".dcr" => "application/x-director",
  133. ".dd2" => "application/vnd.oma.dd2+xml",
  134. ".ddd" => "application/vnd.fujixerox.ddd",
  135. ".deb" => "application/x-debian-package",
  136. ".der" => "application/x-x509-ca-cert",
  137. ".dfac" => "application/vnd.dreamfactory",
  138. ".diff" => "text/x-diff",
  139. ".dis" => "application/vnd.mobius.dis",
  140. ".djv" => "image/vnd.djvu",
  141. ".djvu" => "image/vnd.djvu",
  142. ".dll" => "application/x-msdownload",
  143. ".dmg" => "application/octet-stream",
  144. ".dna" => "application/vnd.dna",
  145. ".doc" => "application/msword",
  146. ".docm" => "application/vnd.ms-word.document.macroEnabled.12",
  147. ".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
  148. ".dot" => "application/msword",
  149. ".dotm" => "application/vnd.ms-word.template.macroEnabled.12",
  150. ".dotx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.template",
  151. ".dp" => "application/vnd.osgi.dp",
  152. ".dpg" => "application/vnd.dpgraph",
  153. ".dsc" => "text/prs.lines.tag",
  154. ".dtd" => "application/xml-dtd",
  155. ".dts" => "audio/vnd.dts",
  156. ".dtshd" => "audio/vnd.dts.hd",
  157. ".dv" => "video/x-dv",
  158. ".dvi" => "application/x-dvi",
  159. ".dwf" => "model/vnd.dwf",
  160. ".dwg" => "image/vnd.dwg",
  161. ".dxf" => "image/vnd.dxf",
  162. ".dxp" => "application/vnd.spotfire.dxp",
  163. ".ear" => "application/java-archive",
  164. ".ecelp4800" => "audio/vnd.nuera.ecelp4800",
  165. ".ecelp7470" => "audio/vnd.nuera.ecelp7470",
  166. ".ecelp9600" => "audio/vnd.nuera.ecelp9600",
  167. ".ecma" => "application/ecmascript",
  168. ".edm" => "application/vnd.novadigm.edm",
  169. ".edx" => "application/vnd.novadigm.edx",
  170. ".efif" => "application/vnd.picsel",
  171. ".ei6" => "application/vnd.pg.osasli",
  172. ".eml" => "message/rfc822",
  173. ".eol" => "audio/vnd.digital-winds",
  174. ".eot" => "application/vnd.ms-fontobject",
  175. ".eps" => "application/postscript",
  176. ".es3" => "application/vnd.eszigno3+xml",
  177. ".esf" => "application/vnd.epson.esf",
  178. ".etx" => "text/x-setext",
  179. ".exe" => "application/x-msdownload",
  180. ".ext" => "application/vnd.novadigm.ext",
  181. ".ez" => "application/andrew-inset",
  182. ".ez2" => "application/vnd.ezpix-album",
  183. ".ez3" => "application/vnd.ezpix-package",
  184. ".f" => "text/x-fortran",
  185. ".f77" => "text/x-fortran",
  186. ".f90" => "text/x-fortran",
  187. ".fbs" => "image/vnd.fastbidsheet",
  188. ".fdf" => "application/vnd.fdf",
  189. ".fe_launch" => "application/vnd.denovo.fcselayout-link",
  190. ".fg5" => "application/vnd.fujitsu.oasysgp",
  191. ".fli" => "video/x-fli",
  192. ".flo" => "application/vnd.micrografx.flo",
  193. ".flv" => "video/x-flv",
  194. ".flw" => "application/vnd.kde.kivio",
  195. ".flx" => "text/vnd.fmi.flexstor",
  196. ".fly" => "text/vnd.fly",
  197. ".fm" => "application/vnd.framemaker",
  198. ".fnc" => "application/vnd.frogans.fnc",
  199. ".for" => "text/x-fortran",
  200. ".fpx" => "image/vnd.fpx",
  201. ".fsc" => "application/vnd.fsc.weblaunch",
  202. ".fst" => "image/vnd.fst",
  203. ".ftc" => "application/vnd.fluxtime.clip",
  204. ".fti" => "application/vnd.anser-web-funds-transfer-initiation",
  205. ".fvt" => "video/vnd.fvt",
  206. ".fzs" => "application/vnd.fuzzysheet",
  207. ".g3" => "image/g3fax",
  208. ".gac" => "application/vnd.groove-account",
  209. ".gdl" => "model/vnd.gdl",
  210. ".gem" => "application/octet-stream",
  211. ".gemspec" => "text/x-script.ruby",
  212. ".ghf" => "application/vnd.groove-help",
  213. ".gif" => "image/gif",
  214. ".gim" => "application/vnd.groove-identity-message",
  215. ".gmx" => "application/vnd.gmx",
  216. ".gph" => "application/vnd.flographit",
  217. ".gqf" => "application/vnd.grafeq",
  218. ".gram" => "application/srgs",
  219. ".grv" => "application/vnd.groove-injector",
  220. ".grxml" => "application/srgs+xml",
  221. ".gtar" => "application/x-gtar",
  222. ".gtm" => "application/vnd.groove-tool-message",
  223. ".gtw" => "model/vnd.gtw",
  224. ".gv" => "text/vnd.graphviz",
  225. ".gz" => "application/x-gzip",
  226. ".h" => "text/x-c",
  227. ".h261" => "video/h261",
  228. ".h263" => "video/h263",
  229. ".h264" => "video/h264",
  230. ".hbci" => "application/vnd.hbci",
  231. ".hdf" => "application/x-hdf",
  232. ".hh" => "text/x-c",
  233. ".hlp" => "application/winhlp",
  234. ".hpgl" => "application/vnd.hp-hpgl",
  235. ".hpid" => "application/vnd.hp-hpid",
  236. ".hps" => "application/vnd.hp-hps",
  237. ".hqx" => "application/mac-binhex40",
  238. ".htc" => "text/x-component",
  239. ".htke" => "application/vnd.kenameaapp",
  240. ".htm" => "text/html",
  241. ".html" => "text/html",
  242. ".hvd" => "application/vnd.yamaha.hv-dic",
  243. ".hvp" => "application/vnd.yamaha.hv-voice",
  244. ".hvs" => "application/vnd.yamaha.hv-script",
  245. ".icc" => "application/vnd.iccprofile",
  246. ".ice" => "x-conference/x-cooltalk",
  247. ".ico" => "image/vnd.microsoft.icon",
  248. ".ics" => "text/calendar",
  249. ".ief" => "image/ief",
  250. ".ifb" => "text/calendar",
  251. ".ifm" => "application/vnd.shana.informed.formdata",
  252. ".igl" => "application/vnd.igloader",
  253. ".igs" => "model/iges",
  254. ".igx" => "application/vnd.micrografx.igx",
  255. ".iif" => "application/vnd.shana.informed.interchange",
  256. ".imp" => "application/vnd.accpac.simply.imp",
  257. ".ims" => "application/vnd.ms-ims",
  258. ".ipk" => "application/vnd.shana.informed.package",
  259. ".irm" => "application/vnd.ibm.rights-management",
  260. ".irp" => "application/vnd.irepository.package+xml",
  261. ".iso" => "application/octet-stream",
  262. ".itp" => "application/vnd.shana.informed.formtemplate",
  263. ".ivp" => "application/vnd.immervision-ivp",
  264. ".ivu" => "application/vnd.immervision-ivu",
  265. ".jad" => "text/vnd.sun.j2me.app-descriptor",
  266. ".jam" => "application/vnd.jam",
  267. ".jar" => "application/java-archive",
  268. ".java" => "text/x-java-source",
  269. ".jisp" => "application/vnd.jisp",
  270. ".jlt" => "application/vnd.hp-jlyt",
  271. ".jnlp" => "application/x-java-jnlp-file",
  272. ".joda" => "application/vnd.joost.joda-archive",
  273. ".jp2" => "image/jp2",
  274. ".jpeg" => "image/jpeg",
  275. ".jpg" => "image/jpeg",
  276. ".jpgv" => "video/jpeg",
  277. ".jpm" => "video/jpm",
  278. ".js" => "application/javascript",
  279. ".json" => "application/json",
  280. ".karbon" => "application/vnd.kde.karbon",
  281. ".kfo" => "application/vnd.kde.kformula",
  282. ".kia" => "application/vnd.kidspiration",
  283. ".kml" => "application/vnd.google-earth.kml+xml",
  284. ".kmz" => "application/vnd.google-earth.kmz",
  285. ".kne" => "application/vnd.kinar",
  286. ".kon" => "application/vnd.kde.kontour",
  287. ".kpr" => "application/vnd.kde.kpresenter",
  288. ".ksp" => "application/vnd.kde.kspread",
  289. ".ktz" => "application/vnd.kahootz",
  290. ".kwd" => "application/vnd.kde.kword",
  291. ".latex" => "application/x-latex",
  292. ".lbd" => "application/vnd.llamagraphics.life-balance.desktop",
  293. ".lbe" => "application/vnd.llamagraphics.life-balance.exchange+xml",
  294. ".les" => "application/vnd.hhe.lesson-player",
  295. ".link66" => "application/vnd.route66.link66+xml",
  296. ".log" => "text/plain",
  297. ".lostxml" => "application/lost+xml",
  298. ".lrm" => "application/vnd.ms-lrm",
  299. ".ltf" => "application/vnd.frogans.ltf",
  300. ".lvp" => "audio/vnd.lucent.voice",
  301. ".lwp" => "application/vnd.lotus-wordpro",
  302. ".m3u" => "audio/x-mpegurl",
  303. ".m4a" => "audio/mp4a-latm",
  304. ".m4v" => "video/mp4",
  305. ".ma" => "application/mathematica",
  306. ".mag" => "application/vnd.ecowin.chart",
  307. ".man" => "text/troff",
  308. ".manifest" => "text/cache-manifest",
  309. ".mathml" => "application/mathml+xml",
  310. ".mbk" => "application/vnd.mobius.mbk",
  311. ".mbox" => "application/mbox",
  312. ".mc1" => "application/vnd.medcalcdata",
  313. ".mcd" => "application/vnd.mcd",
  314. ".mdb" => "application/x-msaccess",
  315. ".mdi" => "image/vnd.ms-modi",
  316. ".mdoc" => "text/troff",
  317. ".me" => "text/troff",
  318. ".mfm" => "application/vnd.mfmp",
  319. ".mgz" => "application/vnd.proteus.magazine",
  320. ".mid" => "audio/midi",
  321. ".midi" => "audio/midi",
  322. ".mif" => "application/vnd.mif",
  323. ".mime" => "message/rfc822",
  324. ".mj2" => "video/mj2",
  325. ".mlp" => "application/vnd.dolby.mlp",
  326. ".mmd" => "application/vnd.chipnuts.karaoke-mmd",
  327. ".mmf" => "application/vnd.smaf",
  328. ".mml" => "application/mathml+xml",
  329. ".mmr" => "image/vnd.fujixerox.edmics-mmr",
  330. ".mng" => "video/x-mng",
  331. ".mny" => "application/x-msmoney",
  332. ".mov" => "video/quicktime",
  333. ".movie" => "video/x-sgi-movie",
  334. ".mp3" => "audio/mpeg",
  335. ".mp4" => "video/mp4",
  336. ".mp4a" => "audio/mp4",
  337. ".mp4s" => "application/mp4",
  338. ".mp4v" => "video/mp4",
  339. ".mpc" => "application/vnd.mophun.certificate",
  340. ".mpeg" => "video/mpeg",
  341. ".mpg" => "video/mpeg",
  342. ".mpga" => "audio/mpeg",
  343. ".mpkg" => "application/vnd.apple.installer+xml",
  344. ".mpm" => "application/vnd.blueice.multipass",
  345. ".mpn" => "application/vnd.mophun.application",
  346. ".mpp" => "application/vnd.ms-project",
  347. ".mpy" => "application/vnd.ibm.minipay",
  348. ".mqy" => "application/vnd.mobius.mqy",
  349. ".mrc" => "application/marc",
  350. ".ms" => "text/troff",
  351. ".mscml" => "application/mediaservercontrol+xml",
  352. ".mseq" => "application/vnd.mseq",
  353. ".msf" => "application/vnd.epson.msf",
  354. ".msh" => "model/mesh",
  355. ".msi" => "application/x-msdownload",
  356. ".msl" => "application/vnd.mobius.msl",
  357. ".msty" => "application/vnd.muvee.style",
  358. ".mts" => "model/vnd.mts",
  359. ".mus" => "application/vnd.musician",
  360. ".mvb" => "application/x-msmediaview",
  361. ".mwf" => "application/vnd.mfer",
  362. ".mxf" => "application/mxf",
  363. ".mxl" => "application/vnd.recordare.musicxml",
  364. ".mxml" => "application/xv+xml",
  365. ".mxs" => "application/vnd.triscape.mxs",
  366. ".mxu" => "video/vnd.mpegurl",
  367. ".n" => "application/vnd.nokia.n-gage.symbian.install",
  368. ".nc" => "application/x-netcdf",
  369. ".ngdat" => "application/vnd.nokia.n-gage.data",
  370. ".nlu" => "application/vnd.neurolanguage.nlu",
  371. ".nml" => "application/vnd.enliven",
  372. ".nnd" => "application/vnd.noblenet-directory",
  373. ".nns" => "application/vnd.noblenet-sealer",
  374. ".nnw" => "application/vnd.noblenet-web",
  375. ".npx" => "image/vnd.net-fpx",
  376. ".nsf" => "application/vnd.lotus-notes",
  377. ".oa2" => "application/vnd.fujitsu.oasys2",
  378. ".oa3" => "application/vnd.fujitsu.oasys3",
  379. ".oas" => "application/vnd.fujitsu.oasys",
  380. ".obd" => "application/x-msbinder",
  381. ".oda" => "application/oda",
  382. ".odc" => "application/vnd.oasis.opendocument.chart",
  383. ".odf" => "application/vnd.oasis.opendocument.formula",
  384. ".odg" => "application/vnd.oasis.opendocument.graphics",
  385. ".odi" => "application/vnd.oasis.opendocument.image",
  386. ".odp" => "application/vnd.oasis.opendocument.presentation",
  387. ".ods" => "application/vnd.oasis.opendocument.spreadsheet",
  388. ".odt" => "application/vnd.oasis.opendocument.text",
  389. ".oga" => "audio/ogg",
  390. ".ogg" => "application/ogg",
  391. ".ogv" => "video/ogg",
  392. ".ogx" => "application/ogg",
  393. ".org" => "application/vnd.lotus-organizer",
  394. ".otc" => "application/vnd.oasis.opendocument.chart-template",
  395. ".otf" => "application/vnd.oasis.opendocument.formula-template",
  396. ".otg" => "application/vnd.oasis.opendocument.graphics-template",
  397. ".oth" => "application/vnd.oasis.opendocument.text-web",
  398. ".oti" => "application/vnd.oasis.opendocument.image-template",
  399. ".otm" => "application/vnd.oasis.opendocument.text-master",
  400. ".ots" => "application/vnd.oasis.opendocument.spreadsheet-template",
  401. ".ott" => "application/vnd.oasis.opendocument.text-template",
  402. ".oxt" => "application/vnd.openofficeorg.extension",
  403. ".p" => "text/x-pascal",
  404. ".p10" => "application/pkcs10",
  405. ".p12" => "application/x-pkcs12",
  406. ".p7b" => "application/x-pkcs7-certificates",
  407. ".p7m" => "application/pkcs7-mime",
  408. ".p7r" => "application/x-pkcs7-certreqresp",
  409. ".p7s" => "application/pkcs7-signature",
  410. ".pas" => "text/x-pascal",
  411. ".pbd" => "application/vnd.powerbuilder6",
  412. ".pbm" => "image/x-portable-bitmap",
  413. ".pcl" => "application/vnd.hp-pcl",
  414. ".pclxl" => "application/vnd.hp-pclxl",
  415. ".pcx" => "image/x-pcx",
  416. ".pdb" => "chemical/x-pdb",
  417. ".pdf" => "application/pdf",
  418. ".pem" => "application/x-x509-ca-cert",
  419. ".pfr" => "application/font-tdpfr",
  420. ".pgm" => "image/x-portable-graymap",
  421. ".pgn" => "application/x-chess-pgn",
  422. ".pgp" => "application/pgp-encrypted",
  423. ".pic" => "image/x-pict",
  424. ".pict" => "image/pict",
  425. ".pkg" => "application/octet-stream",
  426. ".pki" => "application/pkixcmp",
  427. ".pkipath" => "application/pkix-pkipath",
  428. ".pl" => "text/x-script.perl",
  429. ".plb" => "application/vnd.3gpp.pic-bw-large",
  430. ".plc" => "application/vnd.mobius.plc",
  431. ".plf" => "application/vnd.pocketlearn",
  432. ".pls" => "application/pls+xml",
  433. ".pm" => "text/x-script.perl-module",
  434. ".pml" => "application/vnd.ctc-posml",
  435. ".png" => "image/png",
  436. ".pnm" => "image/x-portable-anymap",
  437. ".pntg" => "image/x-macpaint",
  438. ".portpkg" => "application/vnd.macports.portpkg",
  439. ".pot" => "application/vnd.ms-powerpoint",
  440. ".potm" => "application/vnd.ms-powerpoint.template.macroEnabled.12",
  441. ".potx" => "application/vnd.openxmlformats-officedocument.presentationml.template",
  442. ".ppa" => "application/vnd.ms-powerpoint",
  443. ".ppam" => "application/vnd.ms-powerpoint.addin.macroEnabled.12",
  444. ".ppd" => "application/vnd.cups-ppd",
  445. ".ppm" => "image/x-portable-pixmap",
  446. ".pps" => "application/vnd.ms-powerpoint",
  447. ".ppsm" => "application/vnd.ms-powerpoint.slideshow.macroEnabled.12",
  448. ".ppsx" => "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
  449. ".ppt" => "application/vnd.ms-powerpoint",
  450. ".pptm" => "application/vnd.ms-powerpoint.presentation.macroEnabled.12",
  451. ".pptx" => "application/vnd.openxmlformats-officedocument.presentationml.presentation",
  452. ".prc" => "application/vnd.palm",
  453. ".pre" => "application/vnd.lotus-freelance",
  454. ".prf" => "application/pics-rules",
  455. ".ps" => "application/postscript",
  456. ".psb" => "application/vnd.3gpp.pic-bw-small",
  457. ".psd" => "image/vnd.adobe.photoshop",
  458. ".ptid" => "application/vnd.pvi.ptid1",
  459. ".pub" => "application/x-mspublisher",
  460. ".pvb" => "application/vnd.3gpp.pic-bw-var",
  461. ".pwn" => "application/vnd.3m.post-it-notes",
  462. ".py" => "text/x-script.python",
  463. ".pya" => "audio/vnd.ms-playready.media.pya",
  464. ".pyv" => "video/vnd.ms-playready.media.pyv",
  465. ".qam" => "application/vnd.epson.quickanime",
  466. ".qbo" => "application/vnd.intu.qbo",
  467. ".qfx" => "application/vnd.intu.qfx",
  468. ".qps" => "application/vnd.publishare-delta-tree",
  469. ".qt" => "video/quicktime",
  470. ".qtif" => "image/x-quicktime",
  471. ".qxd" => "application/vnd.quark.quarkxpress",
  472. ".ra" => "audio/x-pn-realaudio",
  473. ".rake" => "text/x-script.ruby",
  474. ".ram" => "audio/x-pn-realaudio",
  475. ".rar" => "application/x-rar-compressed",
  476. ".ras" => "image/x-cmu-raster",
  477. ".rb" => "text/x-script.ruby",
  478. ".rcprofile" => "application/vnd.ipunplugged.rcprofile",
  479. ".rdf" => "application/rdf+xml",
  480. ".rdz" => "application/vnd.data-vision.rdz",
  481. ".rep" => "application/vnd.businessobjects",
  482. ".rgb" => "image/x-rgb",
  483. ".rif" => "application/reginfo+xml",
  484. ".rl" => "application/resource-lists+xml",
  485. ".rlc" => "image/vnd.fujixerox.edmics-rlc",
  486. ".rld" => "application/resource-lists-diff+xml",
  487. ".rm" => "application/vnd.rn-realmedia",
  488. ".rmp" => "audio/x-pn-realaudio-plugin",
  489. ".rms" => "application/vnd.jcp.javame.midlet-rms",
  490. ".rnc" => "application/relax-ng-compact-syntax",
  491. ".roff" => "text/troff",
  492. ".rpm" => "application/x-redhat-package-manager",
  493. ".rpss" => "application/vnd.nokia.radio-presets",
  494. ".rpst" => "application/vnd.nokia.radio-preset",
  495. ".rq" => "application/sparql-query",
  496. ".rs" => "application/rls-services+xml",
  497. ".rsd" => "application/rsd+xml",
  498. ".rss" => "application/rss+xml",
  499. ".rtf" => "application/rtf",
  500. ".rtx" => "text/richtext",
  501. ".ru" => "text/x-script.ruby",
  502. ".s" => "text/x-asm",
  503. ".saf" => "application/vnd.yamaha.smaf-audio",
  504. ".sbml" => "application/sbml+xml",
  505. ".sc" => "application/vnd.ibm.secure-container",
  506. ".scd" => "application/x-msschedule",
  507. ".scm" => "application/vnd.lotus-screencam",
  508. ".scq" => "application/scvp-cv-request",
  509. ".scs" => "application/scvp-cv-response",
  510. ".sdkm" => "application/vnd.solent.sdkm+xml",
  511. ".sdp" => "application/sdp",
  512. ".see" => "application/vnd.seemail",
  513. ".sema" => "application/vnd.sema",
  514. ".semd" => "application/vnd.semd",
  515. ".semf" => "application/vnd.semf",
  516. ".setpay" => "application/set-payment-initiation",
  517. ".setreg" => "application/set-registration-initiation",
  518. ".sfd" => "application/vnd.hydrostatix.sof-data",
  519. ".sfs" => "application/vnd.spotfire.sfs",
  520. ".sgm" => "text/sgml",
  521. ".sgml" => "text/sgml",
  522. ".sh" => "application/x-sh",
  523. ".shar" => "application/x-shar",
  524. ".shf" => "application/shf+xml",
  525. ".sig" => "application/pgp-signature",
  526. ".sit" => "application/x-stuffit",
  527. ".sitx" => "application/x-stuffitx",
  528. ".skp" => "application/vnd.koan",
  529. ".slt" => "application/vnd.epson.salt",
  530. ".smi" => "application/smil+xml",
  531. ".snd" => "audio/basic",
  532. ".so" => "application/octet-stream",
  533. ".spf" => "application/vnd.yamaha.smaf-phrase",
  534. ".spl" => "application/x-futuresplash",
  535. ".spot" => "text/vnd.in3d.spot",
  536. ".spp" => "application/scvp-vp-response",
  537. ".spq" => "application/scvp-vp-request",
  538. ".src" => "application/x-wais-source",
  539. ".srx" => "application/sparql-results+xml",
  540. ".sse" => "application/vnd.kodak-descriptor",
  541. ".ssf" => "application/vnd.epson.ssf",
  542. ".ssml" => "application/ssml+xml",
  543. ".stf" => "application/vnd.wt.stf",
  544. ".stk" => "application/hyperstudio",
  545. ".str" => "application/vnd.pg.format",
  546. ".sus" => "application/vnd.sus-calendar",
  547. ".sv4cpio" => "application/x-sv4cpio",
  548. ".sv4crc" => "application/x-sv4crc",
  549. ".svd" => "application/vnd.svd",
  550. ".svg" => "image/svg+xml",
  551. ".svgz" => "image/svg+xml",
  552. ".swf" => "application/x-shockwave-flash",
  553. ".swi" => "application/vnd.arastra.swi",
  554. ".t" => "text/troff",
  555. ".tao" => "application/vnd.tao.intent-module-archive",
  556. ".tar" => "application/x-tar",
  557. ".tbz" => "application/x-bzip-compressed-tar",
  558. ".tcap" => "application/vnd.3gpp2.tcap",
  559. ".tcl" => "application/x-tcl",
  560. ".tex" => "application/x-tex",
  561. ".texi" => "application/x-texinfo",
  562. ".texinfo" => "application/x-texinfo",
  563. ".text" => "text/plain",
  564. ".tif" => "image/tiff",
  565. ".tiff" => "image/tiff",
  566. ".tmo" => "application/vnd.tmobile-livetv",
  567. ".torrent" => "application/x-bittorrent",
  568. ".tpl" => "application/vnd.groove-tool-template",
  569. ".tpt" => "application/vnd.trid.tpt",
  570. ".tr" => "text/troff",
  571. ".tra" => "application/vnd.trueapp",
  572. ".trm" => "application/x-msterminal",
  573. ".tsv" => "text/tab-separated-values",
  574. ".ttf" => "application/octet-stream",
  575. ".twd" => "application/vnd.simtech-mindmapper",
  576. ".txd" => "application/vnd.genomatix.tuxedo",
  577. ".txf" => "application/vnd.mobius.txf",
  578. ".txt" => "text/plain",
  579. ".ufd" => "application/vnd.ufdl",
  580. ".umj" => "application/vnd.umajin",
  581. ".unityweb" => "application/vnd.unity",
  582. ".uoml" => "application/vnd.uoml+xml",
  583. ".uri" => "text/uri-list",
  584. ".ustar" => "application/x-ustar",
  585. ".utz" => "application/vnd.uiq.theme",
  586. ".uu" => "text/x-uuencode",
  587. ".vcd" => "application/x-cdlink",
  588. ".vcf" => "text/x-vcard",
  589. ".vcg" => "application/vnd.groove-vcard",
  590. ".vcs" => "text/x-vcalendar",
  591. ".vcx" => "application/vnd.vcx",
  592. ".vis" => "application/vnd.visionary",
  593. ".viv" => "video/vnd.vivo",
  594. ".vrml" => "model/vrml",
  595. ".vsd" => "application/vnd.visio",
  596. ".vsf" => "application/vnd.vsf",
  597. ".vtu" => "model/vnd.vtu",
  598. ".vxml" => "application/voicexml+xml",
  599. ".war" => "application/java-archive",
  600. ".wav" => "audio/x-wav",
  601. ".wax" => "audio/x-ms-wax",
  602. ".wbmp" => "image/vnd.wap.wbmp",
  603. ".wbs" => "application/vnd.criticaltools.wbs+xml",
  604. ".wbxml" => "application/vnd.wap.wbxml",
  605. ".webm" => "video/webm",
  606. ".wm" => "video/x-ms-wm",
  607. ".wma" => "audio/x-ms-wma",
  608. ".wmd" => "application/x-ms-wmd",
  609. ".wmf" => "application/x-msmetafile",
  610. ".wml" => "text/vnd.wap.wml",
  611. ".wmlc" => "application/vnd.wap.wmlc",
  612. ".wmls" => "text/vnd.wap.wmlscript",
  613. ".wmlsc" => "application/vnd.wap.wmlscriptc",
  614. ".wmv" => "video/x-ms-wmv",
  615. ".wmx" => "video/x-ms-wmx",
  616. ".wmz" => "application/x-ms-wmz",
  617. ".woff" => "application/font-woff",
  618. ".woff2" => "application/font-woff2",
  619. ".wpd" => "application/vnd.wordperfect",
  620. ".wpl" => "application/vnd.ms-wpl",
  621. ".wps" => "application/vnd.ms-works",
  622. ".wqd" => "application/vnd.wqd",
  623. ".wri" => "application/x-mswrite",
  624. ".wrl" => "model/vrml",
  625. ".wsdl" => "application/wsdl+xml",
  626. ".wspolicy" => "application/wspolicy+xml",
  627. ".wtb" => "application/vnd.webturbo",
  628. ".wvx" => "video/x-ms-wvx",
  629. ".x3d" => "application/vnd.hzn-3d-crossword",
  630. ".xar" => "application/vnd.xara",
  631. ".xbd" => "application/vnd.fujixerox.docuworks.binder",
  632. ".xbm" => "image/x-xbitmap",
  633. ".xdm" => "application/vnd.syncml.dm+xml",
  634. ".xdp" => "application/vnd.adobe.xdp+xml",
  635. ".xdw" => "application/vnd.fujixerox.docuworks",
  636. ".xenc" => "application/xenc+xml",
  637. ".xer" => "application/patch-ops-error+xml",
  638. ".xfdf" => "application/vnd.adobe.xfdf",
  639. ".xfdl" => "application/vnd.xfdl",
  640. ".xhtml" => "application/xhtml+xml",
  641. ".xif" => "image/vnd.xiff",
  642. ".xla" => "application/vnd.ms-excel",
  643. ".xlam" => "application/vnd.ms-excel.addin.macroEnabled.12",
  644. ".xls" => "application/vnd.ms-excel",
  645. ".xlsb" => "application/vnd.ms-excel.sheet.binary.macroEnabled.12",
  646. ".xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
  647. ".xlsm" => "application/vnd.ms-excel.sheet.macroEnabled.12",
  648. ".xlt" => "application/vnd.ms-excel",
  649. ".xltx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
  650. ".xml" => "application/xml",
  651. ".xo" => "application/vnd.olpc-sugar",
  652. ".xop" => "application/xop+xml",
  653. ".xpm" => "image/x-xpixmap",
  654. ".xpr" => "application/vnd.is-xpr",
  655. ".xps" => "application/vnd.ms-xpsdocument",
  656. ".xpw" => "application/vnd.intercon.formnet",
  657. ".xsl" => "application/xml",
  658. ".xslt" => "application/xslt+xml",
  659. ".xsm" => "application/vnd.syncml+xml",
  660. ".xspf" => "application/xspf+xml",
  661. ".xul" => "application/vnd.mozilla.xul+xml",
  662. ".xwd" => "image/x-xwindowdump",
  663. ".xyz" => "chemical/x-xyz",
  664. ".yaml" => "text/yaml",
  665. ".yml" => "text/yaml",
  666. ".zaz" => "application/vnd.zzazz.deck+xml",
  667. ".zip" => "application/zip",
  668. ".zmm" => "application/vnd.handheld-entertainment+xml",
  669. }
  670. end
  671. end

target/rubygems/gems/rack-2.0.7/lib/rack/mock.rb

66.67% lines covered

96 relevant lines. 64 lines covered and 32 lines missed.
    
  1. 1 require 'uri'
  2. 1 require 'stringio'
  3. 1 require 'rack'
  4. 1 require 'rack/lint'
  5. 1 require 'rack/utils'
  6. 1 require 'rack/response'
  7. 1 module Rack
  8. # Rack::MockRequest helps testing your Rack application without
  9. # actually using HTTP.
  10. #
  11. # After performing a request on a URL with get/post/put/patch/delete, it
  12. # returns a MockResponse with useful helper methods for effective
  13. # testing.
  14. #
  15. # You can pass a hash with additional configuration to the
  16. # get/post/put/patch/delete.
  17. # <tt>:input</tt>:: A String or IO-like to be used as rack.input.
  18. # <tt>:fatal</tt>:: Raise a FatalWarning if the app writes to rack.errors.
  19. # <tt>:lint</tt>:: If true, wrap the application in a Rack::Lint.
  20. 1 class MockRequest
  21. 1 class FatalWarning < RuntimeError
  22. end
  23. 1 class FatalWarner
  24. 1 def puts(warning)
  25. raise FatalWarning, warning
  26. end
  27. 1 def write(warning)
  28. raise FatalWarning, warning
  29. end
  30. 1 def flush
  31. end
  32. 1 def string
  33. ""
  34. end
  35. end
  36. DEFAULT_ENV = {
  37. 1 RACK_VERSION => Rack::VERSION,
  38. RACK_INPUT => StringIO.new,
  39. RACK_ERRORS => StringIO.new,
  40. RACK_MULTITHREAD => true,
  41. RACK_MULTIPROCESS => true,
  42. RACK_RUNONCE => false,
  43. }.freeze
  44. 1 def initialize(app)
  45. @app = app
  46. end
  47. 1 def get(uri, opts={}) request(GET, uri, opts) end
  48. 1 def post(uri, opts={}) request(POST, uri, opts) end
  49. 1 def put(uri, opts={}) request(PUT, uri, opts) end
  50. 1 def patch(uri, opts={}) request(PATCH, uri, opts) end
  51. 1 def delete(uri, opts={}) request(DELETE, uri, opts) end
  52. 1 def head(uri, opts={}) request(HEAD, uri, opts) end
  53. 1 def options(uri, opts={}) request(OPTIONS, uri, opts) end
  54. 1 def request(method=GET, uri="", opts={})
  55. env = self.class.env_for(uri, opts.merge(:method => method))
  56. if opts[:lint]
  57. app = Rack::Lint.new(@app)
  58. else
  59. app = @app
  60. end
  61. errors = env[RACK_ERRORS]
  62. status, headers, body = app.call(env)
  63. MockResponse.new(status, headers, body, errors)
  64. ensure
  65. body.close if body.respond_to?(:close)
  66. end
  67. # For historical reasons, we're pinning to RFC 2396.
  68. # URI::Parser = URI::RFC2396_Parser
  69. 1 def self.parse_uri_rfc2396(uri)
  70. 3 @parser ||= URI::Parser.new
  71. 3 @parser.parse(uri)
  72. end
  73. # Return the Rack environment used for a request to +uri+.
  74. 1 def self.env_for(uri="", opts={})
  75. 3 uri = parse_uri_rfc2396(uri)
  76. 3 uri.path = "/#{uri.path}" unless uri.path[0] == ?/
  77. 3 env = DEFAULT_ENV.dup
  78. 3 env[REQUEST_METHOD] = (opts[:method] ? opts[:method].to_s.upcase : GET).b
  79. 3 env[SERVER_NAME] = (uri.host || "example.org").b
  80. 3 env[SERVER_PORT] = (uri.port ? uri.port.to_s : "80").b
  81. 3 env[QUERY_STRING] = (uri.query.to_s).b
  82. 3 env[PATH_INFO] = ((!uri.path || uri.path.empty?) ? "/" : uri.path).b
  83. 3 env[RACK_URL_SCHEME] = (uri.scheme || "http").b
  84. 3 env[HTTPS] = (env[RACK_URL_SCHEME] == "https" ? "on" : "off").b
  85. 3 env[SCRIPT_NAME] = opts[:script_name] || ""
  86. 3 if opts[:fatal]
  87. env[RACK_ERRORS] = FatalWarner.new
  88. else
  89. 3 env[RACK_ERRORS] = StringIO.new
  90. end
  91. 3 if params = opts[:params]
  92. if env[REQUEST_METHOD] == GET
  93. params = Utils.parse_nested_query(params) if params.is_a?(String)
  94. params.update(Utils.parse_nested_query(env[QUERY_STRING]))
  95. env[QUERY_STRING] = Utils.build_nested_query(params)
  96. elsif !opts.has_key?(:input)
  97. opts["CONTENT_TYPE"] = "application/x-www-form-urlencoded"
  98. if params.is_a?(Hash)
  99. if data = Rack::Multipart.build_multipart(params)
  100. opts[:input] = data
  101. opts["CONTENT_LENGTH"] ||= data.length.to_s
  102. opts["CONTENT_TYPE"] = "multipart/form-data; boundary=#{Rack::Multipart::MULTIPART_BOUNDARY}"
  103. else
  104. opts[:input] = Utils.build_nested_query(params)
  105. end
  106. else
  107. opts[:input] = params
  108. end
  109. end
  110. end
  111. 3 empty_str = String.new
  112. 3 opts[:input] ||= empty_str
  113. 3 if String === opts[:input]
  114. 3 rack_input = StringIO.new(opts[:input])
  115. else
  116. rack_input = opts[:input]
  117. end
  118. 3 rack_input.set_encoding(Encoding::BINARY)
  119. 3 env[RACK_INPUT] = rack_input
  120. 3 env["CONTENT_LENGTH"] ||= env[RACK_INPUT].length.to_s
  121. 3 opts.each { |field, value|
  122. 30 env[field] = value if String === field
  123. }
  124. 3 env
  125. end
  126. end
  127. # Rack::MockResponse provides useful helpers for testing your apps.
  128. # Usually, you don't create the MockResponse on your own, but use
  129. # MockRequest.
  130. 1 class MockResponse < Rack::Response
  131. # Headers
  132. 1 attr_reader :original_headers
  133. # Errors
  134. 1 attr_accessor :errors
  135. 1 def initialize(status, headers, body, errors=StringIO.new(""))
  136. 2 @original_headers = headers
  137. 2 @errors = errors.string if errors.respond_to?(:string)
  138. 2 super(body, status, headers)
  139. end
  140. 1 def =~(other)
  141. body =~ other
  142. end
  143. 1 def match(other)
  144. body.match other
  145. end
  146. 1 def body
  147. # FIXME: apparently users of MockResponse expect the return value of
  148. # MockResponse#body to be a string. However, the real response object
  149. # returns the body as a list.
  150. #
  151. # See spec_showstatus.rb:
  152. #
  153. # should "not replace existing messages" do
  154. # ...
  155. # res.body.should == "foo!"
  156. # end
  157. 2 super.join
  158. end
  159. 1 def empty?
  160. [201, 204, 304].include? status
  161. end
  162. end
  163. end

target/rubygems/gems/rack-2.0.7/lib/rack/runtime.rb

100.0% lines covered

16 relevant lines. 16 lines covered and 0 lines missed.
    
  1. 1 require 'rack/utils'
  2. 1 module Rack
  3. # Sets an "X-Runtime" response header, indicating the response
  4. # time of the request, in seconds
  5. #
  6. # You can put it right before the application to see the processing
  7. # time, or before all the other middlewares to include time for them,
  8. # too.
  9. 1 class Runtime
  10. 1 FORMAT_STRING = "%0.6f".freeze # :nodoc:
  11. 1 HEADER_NAME = "X-Runtime".freeze # :nodoc:
  12. 1 def initialize(app, name = nil)
  13. 1 @app = app
  14. 1 @header_name = HEADER_NAME
  15. 1 @header_name += "-#{name}" if name
  16. end
  17. 1 def call(env)
  18. 2 start_time = Utils.clock_time
  19. 2 status, headers, body = @app.call(env)
  20. 2 request_time = Utils.clock_time - start_time
  21. 2 unless headers.has_key?(@header_name)
  22. 2 headers[@header_name] = FORMAT_STRING % request_time
  23. end
  24. 2 [status, headers, body]
  25. end
  26. end
  27. end

target/rubygems/gems/rack-2.0.7/lib/rack/sendfile.rb

40.54% lines covered

37 relevant lines. 15 lines covered and 22 lines missed.
    
  1. 1 require 'rack/file'
  2. 1 require 'rack/body_proxy'
  3. 1 module Rack
  4. # = Sendfile
  5. #
  6. # The Sendfile middleware intercepts responses whose body is being
  7. # served from a file and replaces it with a server specific X-Sendfile
  8. # header. The web server is then responsible for writing the file contents
  9. # to the client. This can dramatically reduce the amount of work required
  10. # by the Ruby backend and takes advantage of the web server's optimized file
  11. # delivery code.
  12. #
  13. # In order to take advantage of this middleware, the response body must
  14. # respond to +to_path+ and the request must include an X-Sendfile-Type
  15. # header. Rack::File and other components implement +to_path+ so there's
  16. # rarely anything you need to do in your application. The X-Sendfile-Type
  17. # header is typically set in your web servers configuration. The following
  18. # sections attempt to document
  19. #
  20. # === Nginx
  21. #
  22. # Nginx supports the X-Accel-Redirect header. This is similar to X-Sendfile
  23. # but requires parts of the filesystem to be mapped into a private URL
  24. # hierarchy.
  25. #
  26. # The following example shows the Nginx configuration required to create
  27. # a private "/files/" area, enable X-Accel-Redirect, and pass the special
  28. # X-Sendfile-Type and X-Accel-Mapping headers to the backend:
  29. #
  30. # location ~ /files/(.*) {
  31. # internal;
  32. # alias /var/www/$1;
  33. # }
  34. #
  35. # location / {
  36. # proxy_redirect off;
  37. #
  38. # proxy_set_header Host $host;
  39. # proxy_set_header X-Real-IP $remote_addr;
  40. # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  41. #
  42. # proxy_set_header X-Sendfile-Type X-Accel-Redirect;
  43. # proxy_set_header X-Accel-Mapping /var/www/=/files/;
  44. #
  45. # proxy_pass http://127.0.0.1:8080/;
  46. # }
  47. #
  48. # Note that the X-Sendfile-Type header must be set exactly as shown above.
  49. # The X-Accel-Mapping header should specify the location on the file system,
  50. # followed by an equals sign (=), followed name of the private URL pattern
  51. # that it maps to. The middleware performs a simple substitution on the
  52. # resulting path.
  53. #
  54. # See Also: http://wiki.codemongers.com/NginxXSendfile
  55. #
  56. # === lighttpd
  57. #
  58. # Lighttpd has supported some variation of the X-Sendfile header for some
  59. # time, although only recent version support X-Sendfile in a reverse proxy
  60. # configuration.
  61. #
  62. # $HTTP["host"] == "example.com" {
  63. # proxy-core.protocol = "http"
  64. # proxy-core.balancer = "round-robin"
  65. # proxy-core.backends = (
  66. # "127.0.0.1:8000",
  67. # "127.0.0.1:8001",
  68. # ...
  69. # )
  70. #
  71. # proxy-core.allow-x-sendfile = "enable"
  72. # proxy-core.rewrite-request = (
  73. # "X-Sendfile-Type" => (".*" => "X-Sendfile")
  74. # )
  75. # }
  76. #
  77. # See Also: http://redmine.lighttpd.net/wiki/lighttpd/Docs:ModProxyCore
  78. #
  79. # === Apache
  80. #
  81. # X-Sendfile is supported under Apache 2.x using a separate module:
  82. #
  83. # https://tn123.org/mod_xsendfile/
  84. #
  85. # Once the module is compiled and installed, you can enable it using
  86. # XSendFile config directive:
  87. #
  88. # RequestHeader Set X-Sendfile-Type X-Sendfile
  89. # ProxyPassReverse / http://localhost:8001/
  90. # XSendFile on
  91. #
  92. # === Mapping parameter
  93. #
  94. # The third parameter allows for an overriding extension of the
  95. # X-Accel-Mapping header. Mappings should be provided in tuples of internal to
  96. # external. The internal values may contain regular expression syntax, they
  97. # will be matched with case indifference.
  98. 1 class Sendfile
  99. 1 def initialize(app, variation=nil, mappings=[])
  100. 1 @app = app
  101. 1 @variation = variation
  102. 1 @mappings = mappings.map do |internal, external|
  103. [/^#{internal}/i, external]
  104. end
  105. end
  106. 1 def call(env)
  107. 2 status, headers, body = @app.call(env)
  108. 2 if body.respond_to?(:to_path)
  109. case type = variation(env)
  110. when 'X-Accel-Redirect'
  111. path = ::File.expand_path(body.to_path)
  112. if url = map_accel_path(env, path)
  113. headers[CONTENT_LENGTH] = '0'
  114. headers[type] = url
  115. obody = body
  116. body = Rack::BodyProxy.new([]) do
  117. obody.close if obody.respond_to?(:close)
  118. end
  119. else
  120. env[RACK_ERRORS].puts "X-Accel-Mapping header missing"
  121. end
  122. when 'X-Sendfile', 'X-Lighttpd-Send-File'
  123. path = ::File.expand_path(body.to_path)
  124. headers[CONTENT_LENGTH] = '0'
  125. headers[type] = path
  126. obody = body
  127. body = Rack::BodyProxy.new([]) do
  128. obody.close if obody.respond_to?(:close)
  129. end
  130. when '', nil
  131. else
  132. env[RACK_ERRORS].puts "Unknown x-sendfile variation: '#{type}'.\n"
  133. end
  134. end
  135. 2 [status, headers, body]
  136. end
  137. 1 private
  138. 1 def variation(env)
  139. @variation ||
  140. env['sendfile.type'] ||
  141. env['HTTP_X_SENDFILE_TYPE']
  142. end
  143. 1 def map_accel_path(env, path)
  144. if mapping = @mappings.find { |internal,_| internal =~ path }
  145. path.sub(*mapping)
  146. elsif mapping = env['HTTP_X_ACCEL_MAPPING']
  147. internal, external = mapping.split('=', 2).map(&:strip)
  148. path.sub(/^#{internal}/i, external)
  149. end
  150. end
  151. end
  152. end

target/rubygems/gems/rack-2.0.7/lib/rack/session/cookie.rb

42.86% lines covered

84 relevant lines. 36 lines covered and 48 lines missed.
    
  1. 1 require 'openssl'
  2. 1 require 'zlib'
  3. 1 require 'rack/request'
  4. 1 require 'rack/response'
  5. 1 require 'rack/session/abstract/id'
  6. 1 require 'json'
  7. 1 module Rack
  8. 1 module Session
  9. # Rack::Session::Cookie provides simple cookie based session management.
  10. # By default, the session is a Ruby Hash stored as base64 encoded marshalled
  11. # data set to :key (default: rack.session). The object that encodes the
  12. # session data is configurable and must respond to +encode+ and +decode+.
  13. # Both methods must take a string and return a string.
  14. #
  15. # When the secret key is set, cookie data is checked for data integrity.
  16. # The old secret key is also accepted and allows graceful secret rotation.
  17. #
  18. # Example:
  19. #
  20. # use Rack::Session::Cookie, :key => 'rack.session',
  21. # :domain => 'foo.com',
  22. # :path => '/',
  23. # :expire_after => 2592000,
  24. # :secret => 'change_me',
  25. # :old_secret => 'also_change_me'
  26. #
  27. # All parameters are optional.
  28. #
  29. # Example of a cookie with no encoding:
  30. #
  31. # Rack::Session::Cookie.new(application, {
  32. # :coder => Rack::Session::Cookie::Identity.new
  33. # })
  34. #
  35. # Example of a cookie with custom encoding:
  36. #
  37. # Rack::Session::Cookie.new(application, {
  38. # :coder => Class.new {
  39. # def encode(str); str.reverse; end
  40. # def decode(str); str.reverse; end
  41. # }.new
  42. # })
  43. #
  44. 1 class Cookie < Abstract::Persisted
  45. # Encode session cookies as Base64
  46. 1 class Base64
  47. 1 def encode(str)
  48. [str].pack('m')
  49. end
  50. 1 def decode(str)
  51. str.unpack('m').first
  52. end
  53. # Encode session cookies as Marshaled Base64 data
  54. 1 class Marshal < Base64
  55. 1 def encode(str)
  56. super(::Marshal.dump(str))
  57. end
  58. 1 def decode(str)
  59. return unless str
  60. ::Marshal.load(super(str)) rescue nil
  61. end
  62. end
  63. # N.B. Unlike other encoding methods, the contained objects must be a
  64. # valid JSON composite type, either a Hash or an Array.
  65. 1 class JSON < Base64
  66. 1 def encode(obj)
  67. super(::JSON.dump(obj))
  68. end
  69. 1 def decode(str)
  70. return unless str
  71. ::JSON.parse(super(str)) rescue nil
  72. end
  73. end
  74. 1 class ZipJSON < Base64
  75. 1 def encode(obj)
  76. super(Zlib::Deflate.deflate(::JSON.dump(obj)))
  77. end
  78. 1 def decode(str)
  79. return unless str
  80. ::JSON.parse(Zlib::Inflate.inflate(super(str)))
  81. rescue
  82. nil
  83. end
  84. end
  85. end
  86. # Use no encoding for session cookies
  87. 1 class Identity
  88. 1 def encode(str); str; end
  89. 1 def decode(str); str; end
  90. end
  91. 1 attr_reader :coder
  92. 1 def initialize(app, options={})
  93. @secrets = options.values_at(:secret, :old_secret).compact
  94. @hmac = options.fetch(:hmac, OpenSSL::Digest::SHA1)
  95. warn <<-MSG unless secure?(options)
  96. SECURITY WARNING: No secret option provided to Rack::Session::Cookie.
  97. This poses a security threat. It is strongly recommended that you
  98. provide a secret to prevent exploits that may be possible from crafted
  99. cookies. This will not be supported in future versions of Rack, and
  100. future versions will even invalidate your existing user cookies.
  101. Called from: #{caller[0]}.
  102. MSG
  103. @coder = options[:coder] ||= Base64::Marshal.new
  104. super(app, options.merge!(:cookie_only => true))
  105. end
  106. 1 private
  107. 1 def find_session(req, sid)
  108. data = unpacked_cookie_data(req)
  109. data = persistent_session_id!(data)
  110. [data["session_id"], data]
  111. end
  112. 1 def extract_session_id(request)
  113. unpacked_cookie_data(request)["session_id"]
  114. end
  115. 1 def unpacked_cookie_data(request)
  116. request.fetch_header(RACK_SESSION_UNPACKED_COOKIE_DATA) do |k|
  117. session_data = request.cookies[@key]
  118. if @secrets.size > 0 && session_data
  119. digest, session_data = session_data.reverse.split("--", 2)
  120. digest.reverse! if digest
  121. session_data.reverse! if session_data
  122. session_data = nil unless digest_match?(session_data, digest)
  123. end
  124. request.set_header(k, coder.decode(session_data) || {})
  125. end
  126. end
  127. 1 def persistent_session_id!(data, sid=nil)
  128. data ||= {}
  129. data["session_id"] ||= sid || generate_sid
  130. data
  131. end
  132. 1 def write_session(req, session_id, session, options)
  133. session = session.merge("session_id" => session_id)
  134. session_data = coder.encode(session)
  135. if @secrets.first
  136. session_data << "--#{generate_hmac(session_data, @secrets.first)}"
  137. end
  138. if session_data.size > (4096 - @key.size)
  139. req.get_header(RACK_ERRORS).puts("Warning! Rack::Session::Cookie data size exceeds 4K.")
  140. nil
  141. else
  142. session_data
  143. end
  144. end
  145. 1 def delete_session(req, session_id, options)
  146. # Nothing to do here, data is in the client
  147. generate_sid unless options[:drop]
  148. end
  149. 1 def digest_match?(data, digest)
  150. return unless data && digest
  151. @secrets.any? do |secret|
  152. Rack::Utils.secure_compare(digest, generate_hmac(data, secret))
  153. end
  154. end
  155. 1 def generate_hmac(data, secret)
  156. OpenSSL::HMAC.hexdigest(@hmac.new, secret, data)
  157. end
  158. 1 def secure?(options)
  159. @secrets.size >= 1 ||
  160. (options[:coder] && options[:let_coder_handle_secure_encoding])
  161. end
  162. end
  163. end
  164. end

target/rubygems/gems/rack-2.0.7/lib/rack/tempfile_reaper.rb

100.0% lines covered

11 relevant lines. 11 lines covered and 0 lines missed.
    
  1. 1 require 'rack/body_proxy'
  2. 1 module Rack
  3. # Middleware tracks and cleans Tempfiles created throughout a request (i.e. Rack::Multipart)
  4. # Ideas/strategy based on posts by Eric Wong and Charles Oliver Nutter
  5. # https://groups.google.com/forum/#!searchin/rack-devel/temp/rack-devel/brK8eh-MByw/sw61oJJCGRMJ
  6. 1 class TempfileReaper
  7. 1 def initialize(app)
  8. 1 @app = app
  9. end
  10. 1 def call(env)
  11. 2 env[RACK_TEMPFILES] ||= []
  12. 2 status, headers, body = @app.call(env)
  13. 2 body_proxy = BodyProxy.new(body) do
  14. 2 env[RACK_TEMPFILES].each(&:close!) unless env[RACK_TEMPFILES].nil?
  15. end
  16. 2 [status, headers, body_proxy]
  17. end
  18. end
  19. end

target/rubygems/gems/rails-dom-testing-2.0.3/lib/rails-dom-testing.rb

100.0% lines covered

1 relevant lines. 1 lines covered and 0 lines missed.
    
  1. 1 require 'rails/dom/testing/assertions'

target/rubygems/gems/rails-dom-testing-2.0.3/lib/rails/dom/testing/assertions.rb

100.0% lines covered

11 relevant lines. 11 lines covered and 0 lines missed.
    
  1. 1 require 'active_support/concern'
  2. 1 require 'nokogiri'
  3. 1 module Rails
  4. 1 module Dom
  5. 1 module Testing
  6. 1 module Assertions
  7. 1 autoload :DomAssertions, 'rails/dom/testing/assertions/dom_assertions'
  8. 1 autoload :SelectorAssertions, 'rails/dom/testing/assertions/selector_assertions'
  9. 1 extend ActiveSupport::Concern
  10. 1 include DomAssertions
  11. 1 include SelectorAssertions
  12. end
  13. end
  14. end
  15. end

target/rubygems/gems/rails-dom-testing-2.0.3/lib/rails/dom/testing/assertions/dom_assertions.rb

38.89% lines covered

36 relevant lines. 14 lines covered and 22 lines missed.
    
  1. 1 module Rails
  2. 1 module Dom
  3. 1 module Testing
  4. 1 module Assertions
  5. 1 module DomAssertions
  6. # \Test two HTML strings for equivalency (e.g., equal even when attributes are in another order)
  7. #
  8. # # assert that the referenced method generates the appropriate HTML string
  9. # assert_dom_equal '<a href="http://www.example.com">Apples</a>', link_to("Apples", "http://www.example.com")
  10. 1 def assert_dom_equal(expected, actual, message = nil)
  11. expected_dom, actual_dom = fragment(expected), fragment(actual)
  12. message ||= "Expected: #{expected}\nActual: #{actual}"
  13. assert compare_doms(expected_dom, actual_dom), message
  14. end
  15. # The negated form of +assert_dom_equal+.
  16. #
  17. # # assert that the referenced method does not generate the specified HTML string
  18. # assert_dom_not_equal '<a href="http://www.example.com">Apples</a>', link_to("Oranges", "http://www.example.com")
  19. 1 def assert_dom_not_equal(expected, actual, message = nil)
  20. expected_dom, actual_dom = fragment(expected), fragment(actual)
  21. message ||= "Expected: #{expected}\nActual: #{actual}"
  22. assert_not compare_doms(expected_dom, actual_dom), message
  23. end
  24. 1 protected
  25. 1 def compare_doms(expected, actual)
  26. return false unless expected.children.size == actual.children.size
  27. expected.children.each_with_index do |child, i|
  28. return false unless equal_children?(child, actual.children[i])
  29. end
  30. true
  31. end
  32. 1 def equal_children?(child, other_child)
  33. return false unless child.type == other_child.type
  34. if child.element?
  35. child.name == other_child.name &&
  36. equal_attribute_nodes?(child.attribute_nodes, other_child.attribute_nodes) &&
  37. compare_doms(child, other_child)
  38. else
  39. child.to_s == other_child.to_s
  40. end
  41. end
  42. 1 def equal_attribute_nodes?(nodes, other_nodes)
  43. return false unless nodes.size == other_nodes.size
  44. nodes = nodes.sort_by(&:name)
  45. other_nodes = other_nodes.sort_by(&:name)
  46. nodes.each_with_index do |attr, i|
  47. return false unless equal_attribute?(attr, other_nodes[i])
  48. end
  49. true
  50. end
  51. 1 def equal_attribute?(attr, other_attr)
  52. attr.name == other_attr.name && attr.value == other_attr.value
  53. end
  54. 1 private
  55. 1 def fragment(text)
  56. Nokogiri::HTML::DocumentFragment.parse(text)
  57. end
  58. end
  59. end
  60. end
  61. end
  62. end

target/rubygems/gems/rails-dom-testing-2.0.3/lib/rails/dom/testing/assertions/selector_assertions.rb

30.51% lines covered

59 relevant lines. 18 lines covered and 41 lines missed.
    
  1. 1 require 'active_support/deprecation'
  2. 1 require_relative 'selector_assertions/count_describable'
  3. 1 require_relative 'selector_assertions/html_selector'
  4. 1 module Rails
  5. 1 module Dom
  6. 1 module Testing
  7. 1 module Assertions
  8. # Adds the +assert_select+ method for use in Rails functional
  9. # test cases, which can be used to make assertions on the response HTML of a controller
  10. # action. You can also call +assert_select+ within another +assert_select+ to
  11. # make assertions on elements selected by the enclosing assertion.
  12. #
  13. # Use +css_select+ to select elements without making an assertions, either
  14. # from the response HTML or elements selected by the enclosing assertion.
  15. #
  16. # In addition to HTML responses, you can make the following assertions:
  17. #
  18. # * +assert_select_encoded+ - Assertions on HTML encoded inside XML, for example for dealing with feed item descriptions.
  19. # * +assert_select_email+ - Assertions on the HTML body of an e-mail.
  20. 1 module SelectorAssertions
  21. # Select and return all matching elements.
  22. #
  23. # If called with a single argument, uses that argument as a selector.
  24. # Called without an element +css_select+ selects from
  25. # the element returned in +document_root_element+
  26. #
  27. # The default implementation of +document_root_element+ raises an exception explaining this.
  28. #
  29. # Returns an empty Nokogiri::XML::NodeSet if no match is found.
  30. #
  31. # If called with two arguments, uses the first argument as the root
  32. # element and the second argument as the selector. Attempts to match the
  33. # root element and any of its children.
  34. # Returns an empty Nokogiri::XML::NodeSet if no match is found.
  35. #
  36. # The selector may be a CSS selector expression (String).
  37. # css_select returns nil if called with an invalid css selector.
  38. #
  39. # # Selects all div tags
  40. # divs = css_select("div")
  41. #
  42. # # Selects all paragraph tags and does something interesting
  43. # pars = css_select("p")
  44. # pars.each do |par|
  45. # # Do something fun with paragraphs here...
  46. # end
  47. #
  48. # # Selects all list items in unordered lists
  49. # items = css_select("ul>li")
  50. #
  51. # # Selects all form tags and then all inputs inside the form
  52. # forms = css_select("form")
  53. # forms.each do |form|
  54. # inputs = css_select(form, "input")
  55. # ...
  56. # end
  57. 1 def css_select(*args)
  58. raise ArgumentError, "you at least need a selector argument" if args.empty?
  59. root = args.size == 1 ? document_root_element : args.shift
  60. nodeset(root).css(args.first)
  61. end
  62. # An assertion that selects elements and makes one or more equality tests.
  63. #
  64. # If the first argument is an element, selects all matching elements
  65. # starting from (and including) that element and all its children in
  66. # depth-first order.
  67. #
  68. # If no element is specified +assert_select+ selects from
  69. # the element returned in +document_root_element+
  70. # unless +assert_select+ is called from within an +assert_select+ block.
  71. # Override +document_root_element+ to tell +assert_select+ what to select from.
  72. # The default implementation raises an exception explaining this.
  73. #
  74. # When called with a block +assert_select+ passes an array of selected elements
  75. # to the block. Calling +assert_select+ from the block, with no element specified,
  76. # runs the assertion on the complete set of elements selected by the enclosing assertion.
  77. # Alternatively the array may be iterated through so that +assert_select+ can be called
  78. # separately for each element.
  79. #
  80. #
  81. # ==== Example
  82. # If the response contains two ordered lists, each with four list elements then:
  83. # assert_select "ol" do |elements|
  84. # elements.each do |element|
  85. # assert_select element, "li", 4
  86. # end
  87. # end
  88. #
  89. # will pass, as will:
  90. # assert_select "ol" do
  91. # assert_select "li", 8
  92. # end
  93. #
  94. # The selector may be a CSS selector expression (String) or an expression
  95. # with substitution values (Array).
  96. # Substitution uses a custom pseudo class match. Pass in whatever attribute you want to match (enclosed in quotes) and a ? for the substitution.
  97. # assert_select returns nil if called with an invalid css selector.
  98. #
  99. # assert_select "div:match('id', ?)", /\d+/
  100. #
  101. # === Equality Tests
  102. #
  103. # The equality test may be one of the following:
  104. # * <tt>true</tt> - Assertion is true if at least one element selected.
  105. # * <tt>false</tt> - Assertion is true if no element selected.
  106. # * <tt>String/Regexp</tt> - Assertion is true if the text value of at least
  107. # one element matches the string or regular expression.
  108. # * <tt>Integer</tt> - Assertion is true if exactly that number of
  109. # elements are selected.
  110. # * <tt>Range</tt> - Assertion is true if the number of selected
  111. # elements fit the range.
  112. # If no equality test specified, the assertion is true if at least one
  113. # element selected.
  114. #
  115. # To perform more than one equality tests, use a hash with the following keys:
  116. # * <tt>:text</tt> - Narrow the selection to elements that have this text
  117. # value (string or regexp).
  118. # * <tt>:html</tt> - Narrow the selection to elements that have this HTML
  119. # content (string or regexp).
  120. # * <tt>:count</tt> - Assertion is true if the number of selected elements
  121. # is equal to this value.
  122. # * <tt>:minimum</tt> - Assertion is true if the number of selected
  123. # elements is at least this value.
  124. # * <tt>:maximum</tt> - Assertion is true if the number of selected
  125. # elements is at most this value.
  126. #
  127. # If the method is called with a block, once all equality tests are
  128. # evaluated the block is called with an array of all matched elements.
  129. #
  130. # # At least one form element
  131. # assert_select "form"
  132. #
  133. # # Form element includes four input fields
  134. # assert_select "form input", 4
  135. #
  136. # # Page title is "Welcome"
  137. # assert_select "title", "Welcome"
  138. #
  139. # # Page title is "Welcome" and there is only one title element
  140. # assert_select "title", {count: 1, text: "Welcome"},
  141. # "Wrong title or more than one title element"
  142. #
  143. # # Page contains no forms
  144. # assert_select "form", false, "This page must contain no forms"
  145. #
  146. # # Test the content and style
  147. # assert_select "body div.header ul.menu"
  148. #
  149. # # Use substitution values
  150. # assert_select "ol>li:match('id', ?)", /item-\d+/
  151. #
  152. # # All input fields in the form have a name
  153. # assert_select "form input" do
  154. # assert_select ":match('name', ?)", /.+/ # Not empty
  155. # end
  156. 1 def assert_select(*args, &block)
  157. @selected ||= nil
  158. selector = HTMLSelector.new(args, @selected) { nodeset document_root_element }
  159. if selector.selecting_no_body?
  160. assert true
  161. return
  162. end
  163. selector.select.tap do |matches|
  164. assert_size_match!(matches.size, selector.tests,
  165. selector.css_selector, selector.message)
  166. nest_selection(matches, &block) if block_given? && !matches.empty?
  167. end
  168. end
  169. # Extracts the content of an element, treats it as encoded HTML and runs
  170. # nested assertion on it.
  171. #
  172. # You typically call this method within another assertion to operate on
  173. # all currently selected elements. You can also pass an element or array
  174. # of elements.
  175. #
  176. # The content of each element is un-encoded, and wrapped in the root
  177. # element +encoded+. It then calls the block with all un-encoded elements.
  178. #
  179. # # Selects all bold tags from within the title of an Atom feed's entries (perhaps to nab a section name prefix)
  180. # assert_select "feed[xmlns='http://www.w3.org/2005/Atom']" do
  181. # # Select each entry item and then the title item
  182. # assert_select "entry>title" do
  183. # # Run assertions on the encoded title elements
  184. # assert_select_encoded do
  185. # assert_select "b"
  186. # end
  187. # end
  188. # end
  189. #
  190. #
  191. # # Selects all paragraph tags from within the description of an RSS feed
  192. # assert_select "rss[version=2.0]" do
  193. # # Select description element of each feed item.
  194. # assert_select "channel>item>description" do
  195. # # Run assertions on the encoded elements.
  196. # assert_select_encoded do
  197. # assert_select "p"
  198. # end
  199. # end
  200. # end
  201. 1 def assert_select_encoded(element = nil, &block)
  202. if !element && !@selected
  203. raise ArgumentError, "Element is required when called from a nonnested assert_select"
  204. end
  205. content = nodeset(element || @selected).map do |elem|
  206. elem.children.select do |child|
  207. child.cdata? || (child.text? && !child.blank?)
  208. end.map(&:content)
  209. end.join
  210. selected = Nokogiri::HTML::DocumentFragment.parse(content)
  211. nest_selection(selected) do
  212. if content.empty?
  213. yield selected
  214. else
  215. assert_select ":root", &block
  216. end
  217. end
  218. end
  219. # Extracts the body of an email and runs nested assertions on it.
  220. #
  221. # You must enable deliveries for this assertion to work, use:
  222. # ActionMailer::Base.perform_deliveries = true
  223. #
  224. # assert_select_email do
  225. # assert_select "h1", "Email alert"
  226. # end
  227. #
  228. # assert_select_email do
  229. # items = assert_select "ol>li"
  230. # items.each do
  231. # # Work with items here...
  232. # end
  233. # end
  234. 1 def assert_select_email(&block)
  235. deliveries = ActionMailer::Base.deliveries
  236. assert !deliveries.empty?, "No e-mail in delivery list"
  237. deliveries.each do |delivery|
  238. (delivery.parts.empty? ? [delivery] : delivery.parts).each do |part|
  239. if part["Content-Type"].to_s =~ /^text\/html\W/
  240. root = Nokogiri::HTML::DocumentFragment.parse(part.body.to_s)
  241. assert_select root, ":root", &block
  242. end
  243. end
  244. end
  245. end
  246. 1 private
  247. 1 include CountDescribable
  248. 1 def document_root_element
  249. raise NotImplementedError, 'Implementing document_root_element makes ' \
  250. 'assert_select work without needing to specify an element to select from.'
  251. end
  252. # +equals+ must contain :minimum, :maximum and :count keys
  253. 1 def assert_size_match!(size, equals, css_selector, message = nil)
  254. min, max, count = equals[:minimum], equals[:maximum], equals[:count]
  255. message ||= %(Expected #{count_description(min, max, count)} matching "#{css_selector}", found #{size}.)
  256. if count
  257. assert_equal count, size, message
  258. else
  259. assert_operator size, :>=, min, message if min
  260. assert_operator size, :<=, max, message if max
  261. end
  262. end
  263. 1 def nest_selection(selection)
  264. # Set @selected to allow nested assert_select.
  265. # Can be nested several levels deep.
  266. old_selected, @selected = @selected, selection
  267. yield @selected
  268. ensure
  269. @selected = old_selected
  270. end
  271. 1 def nodeset(node)
  272. if node.is_a?(Nokogiri::XML::NodeSet)
  273. node
  274. else
  275. Nokogiri::XML::NodeSet.new(node.document, [node])
  276. end
  277. end
  278. end
  279. end
  280. end
  281. end
  282. end

target/rubygems/gems/rails-dom-testing-2.0.3/lib/rails/dom/testing/assertions/selector_assertions/count_describable.rb

61.11% lines covered

18 relevant lines. 11 lines covered and 7 lines missed.
    
  1. 1 require 'active_support/concern'
  2. 1 module Rails
  3. 1 module Dom
  4. 1 module Testing
  5. 1 module Assertions
  6. 1 module SelectorAssertions
  7. 1 module CountDescribable
  8. 1 extend ActiveSupport::Concern
  9. 1 private
  10. 1 def count_description(min, max, count) #:nodoc:
  11. if min && max && (max != min)
  12. "between #{min} and #{max} elements"
  13. elsif min && max && max == min && count
  14. "exactly #{count} #{pluralize_element(min)}"
  15. elsif min && !(min == 1 && max == 1)
  16. "at least #{min} #{pluralize_element(min)}"
  17. elsif max
  18. "at most #{max} #{pluralize_element(max)}"
  19. end
  20. end
  21. 1 def pluralize_element(quantity)
  22. quantity == 1 ? 'element' : 'elements'
  23. end
  24. end
  25. end
  26. end
  27. end
  28. end
  29. end

target/rubygems/gems/rails-dom-testing-2.0.3/lib/rails/dom/testing/assertions/selector_assertions/html_selector.rb

22.22% lines covered

63 relevant lines. 14 lines covered and 49 lines missed.
    
  1. 1 require 'active_support/core_ext/module/attribute_accessors'
  2. 1 require_relative 'substitution_context'
  3. 1 class HTMLSelector #:nodoc:
  4. 1 attr_reader :css_selector, :tests, :message
  5. 1 def initialize(values, previous_selection = nil, &root_fallback)
  6. @values = values
  7. @root = extract_root(previous_selection, root_fallback)
  8. extract_selectors
  9. @tests = extract_equality_tests
  10. @message = @values.shift
  11. if @values.shift
  12. raise ArgumentError, "Not expecting that last argument, you either have too many arguments, or they're the wrong type"
  13. end
  14. end
  15. 1 def selecting_no_body? #:nodoc:
  16. # Nokogiri gives the document a body element. Which means we can't
  17. # run an assertion expecting there to not be a body.
  18. @selector == 'body' && @tests[:count] == 0
  19. end
  20. 1 def select
  21. filter @root.css(@selector, context)
  22. end
  23. 1 private
  24. 1 NO_STRIP = %w{pre script style textarea}
  25. 2 mattr_reader(:context) { SubstitutionContext.new }
  26. 1 def filter(matches)
  27. match_with = tests[:text] || tests[:html]
  28. return matches if matches.empty? || !match_with
  29. content_mismatch = nil
  30. text_matches = tests.has_key?(:text)
  31. regex_matching = match_with.is_a?(Regexp)
  32. remaining = matches.reject do |match|
  33. # Preserve markup with to_s for html elements
  34. content = text_matches ? match.text : match.children.to_s
  35. content.strip! unless NO_STRIP.include?(match.name)
  36. content.sub!(/\A\n/, '') if text_matches && match.name == "textarea"
  37. next if regex_matching ? (content =~ match_with) : (content == match_with)
  38. content_mismatch ||= sprintf("<%s> expected but was\n<%s>.", match_with, content)
  39. true
  40. end
  41. @message ||= content_mismatch if remaining.empty?
  42. Nokogiri::XML::NodeSet.new(matches.document, remaining)
  43. end
  44. 1 def extract_root(previous_selection, root_fallback)
  45. possible_root = @values.first
  46. if possible_root == nil
  47. raise ArgumentError, 'First argument is either selector or element ' \
  48. 'to select, but nil found. Perhaps you called assert_select with ' \
  49. 'an element that does not exist?'
  50. elsif possible_root.respond_to?(:css)
  51. @values.shift # remove the root, so selector is the first argument
  52. possible_root
  53. elsif previous_selection
  54. previous_selection
  55. else
  56. root_fallback.call
  57. end
  58. end
  59. 1 def extract_selectors
  60. selector = @values.shift
  61. unless selector.is_a? String
  62. raise ArgumentError, "Expecting a selector as the first argument"
  63. end
  64. @css_selector = context.substitute!(selector, @values.dup, true)
  65. @selector = context.substitute!(selector, @values)
  66. end
  67. 1 def extract_equality_tests
  68. comparisons = {}
  69. case comparator = @values.shift
  70. when Hash
  71. comparisons = comparator
  72. when String, Regexp
  73. comparisons[:text] = comparator
  74. when Integer
  75. comparisons[:count] = comparator
  76. when Range
  77. comparisons[:minimum] = comparator.begin
  78. comparisons[:maximum] = comparator.end
  79. when FalseClass
  80. comparisons[:count] = 0
  81. when NilClass, TrueClass
  82. comparisons[:minimum] = 1
  83. else raise ArgumentError, "I don't understand what you're trying to match"
  84. end
  85. # By default we're looking for at least one match.
  86. if comparisons[:count]
  87. comparisons[:minimum] = comparisons[:maximum] = comparisons[:count]
  88. else
  89. comparisons[:minimum] ||= 1
  90. end
  91. comparisons
  92. end
  93. end

target/rubygems/gems/rails-dom-testing-2.0.3/lib/rails/dom/testing/assertions/selector_assertions/substitution_context.rb

47.06% lines covered

17 relevant lines. 8 lines covered and 9 lines missed.
    
  1. 1 class SubstitutionContext
  2. 1 def initialize
  3. 1 @substitute = '?'
  4. end
  5. 1 def substitute!(selector, values, format_for_presentation = false)
  6. selector = selector.dup
  7. while !values.empty? && substitutable?(values.first) && selector.index(@substitute)
  8. selector.sub! @substitute, matcher_for(values.shift, format_for_presentation)
  9. end
  10. selector
  11. end
  12. 1 def match(matches, attribute, matcher)
  13. matches.find_all { |node| node[attribute] =~ Regexp.new(matcher) }
  14. end
  15. 1 private
  16. 1 def matcher_for(value, format_for_presentation)
  17. # Nokogiri doesn't like arbitrary values without quotes, hence inspect.
  18. if format_for_presentation
  19. value.inspect # Avoid to_s so Regexps aren't put in quotes.
  20. else
  21. value.to_s.inspect
  22. end
  23. end
  24. 1 def substitutable?(value)
  25. value.is_a?(String) || value.is_a?(Regexp)
  26. end
  27. end

target/rubygems/gems/railties-5.2.3/lib/minitest/rails_plugin.rb

86.67% lines covered

30 relevant lines. 26 lines covered and 4 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/module/attribute_accessors"
  3. 1 require "rails/test_unit/reporter"
  4. 1 require "rails/test_unit/runner"
  5. 1 module Minitest
  6. 1 class SuppressedSummaryReporter < SummaryReporter
  7. # Disable extra failure output after a run if output is inline.
  8. 1 def aggregated_results(*)
  9. 1 super unless options[:output_inline]
  10. end
  11. end
  12. 1 def self.plugin_rails_options(opts, options)
  13. 1 ::Rails::TestUnit::Runner.attach_before_load_options(opts)
  14. 1 opts.on("-b", "--backtrace", "Show the complete backtrace") do
  15. options[:full_backtrace] = true
  16. end
  17. 1 opts.on("-d", "--defer-output", "Output test failures and errors after the test run") do
  18. options[:output_inline] = false
  19. end
  20. 1 opts.on("-f", "--fail-fast", "Abort test run on first failure or error") do
  21. options[:fail_fast] = true
  22. end
  23. 1 opts.on("-c", "--[no-]color", "Enable color in the output") do |value|
  24. options[:color] = value
  25. end
  26. 1 options[:color] = true
  27. 1 options[:output_inline] = true
  28. end
  29. # Owes great inspiration to test runner trailblazers like RSpec,
  30. # minitest-reporters, maxitest and others.
  31. 1 def self.plugin_rails_init(options)
  32. 1 unless options[:full_backtrace] || ENV["BACKTRACE"]
  33. # Plugin can run without Rails loaded, check before filtering.
  34. 1 Minitest.backtrace_filter = ::Rails.backtrace_cleaner if ::Rails.respond_to?(:backtrace_cleaner)
  35. end
  36. 1 self.plugin_rails_replace_reporters(reporter, options)
  37. end
  38. 1 def self.plugin_rails_replace_reporters(minitest_reporter, options)
  39. 1 return unless minitest_reporter.kind_of?(Minitest::CompositeReporter)
  40. # Replace progress reporter for colors.
  41. 3 if minitest_reporter.reporters.reject! { |reporter| reporter.kind_of?(SummaryReporter) } != nil
  42. 1 minitest_reporter << SuppressedSummaryReporter.new(options[:io], options)
  43. end
  44. 3 if minitest_reporter.reporters.reject! { |reporter| reporter.kind_of?(ProgressReporter) } != nil
  45. 1 minitest_reporter << ::Rails::TestUnitReporter.new(options[:io], options)
  46. end
  47. end
  48. # Backwardscompatibility with Rails 5.0 generated plugin test scripts
  49. 1 mattr_reader :run_via, default: {}
  50. end

target/rubygems/gems/railties-5.2.3/lib/rails/application/bootstrap.rb

80.43% lines covered

46 relevant lines. 37 lines covered and 9 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "fileutils"
  3. 1 require "active_support/notifications"
  4. 1 require "active_support/dependencies"
  5. 1 require "active_support/descendants_tracker"
  6. 1 require "rails/secrets"
  7. 1 module Rails
  8. 1 class Application
  9. 1 module Bootstrap
  10. 1 include Initializable
  11. 1 initializer :load_environment_hook, group: :all do end
  12. 1 initializer :load_active_support, group: :all do
  13. 1 require "active_support/all" unless config.active_support.bare
  14. end
  15. 1 initializer :set_eager_load, group: :all do
  16. 1 if config.eager_load.nil?
  17. warn <<-INFO
  18. config.eager_load is set to nil. Please update your config/environments/*.rb files accordingly:
  19. * development - set it to false
  20. * test - set it to false (unless you use a tool that preloads your test environment)
  21. * production - set it to true
  22. INFO
  23. config.eager_load = config.cache_classes
  24. end
  25. end
  26. # Initialize the logger early in the stack in case we need to log some deprecation.
  27. 1 initializer :initialize_logger, group: :all do
  28. 1 Rails.logger ||= config.logger || begin
  29. 1 path = config.paths["log"].first
  30. 1 unless File.exist? File.dirname path
  31. FileUtils.mkdir_p File.dirname path
  32. end
  33. 1 f = File.open path, "a"
  34. 1 f.binmode
  35. 1 f.sync = config.autoflush_log # if true make sure every write flushes
  36. 1 logger = ActiveSupport::Logger.new f
  37. 1 logger.formatter = config.log_formatter
  38. 1 logger = ActiveSupport::TaggedLogging.new(logger)
  39. 1 logger
  40. rescue StandardError
  41. logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDERR))
  42. logger.level = ActiveSupport::Logger::WARN
  43. logger.warn(
  44. "Rails Error: Unable to access log file. Please ensure that #{path} exists and is writable " \
  45. "(ie, make it writable for user and group: chmod 0664 #{path}). " \
  46. "The log level has been raised to WARN and the output directed to STDERR until the problem is fixed."
  47. )
  48. logger
  49. end
  50. 1 Rails.logger.level = ActiveSupport::Logger.const_get(config.log_level.to_s.upcase)
  51. end
  52. # Initialize cache early in the stack so railties can make use of it.
  53. 1 initializer :initialize_cache, group: :all do
  54. 1 unless Rails.cache
  55. 1 Rails.cache = ActiveSupport::Cache.lookup_store(config.cache_store)
  56. 1 if Rails.cache.respond_to?(:middleware)
  57. 1 config.middleware.insert_before(::Rack::Runtime, Rails.cache.middleware)
  58. end
  59. end
  60. end
  61. # Sets the dependency loading mechanism.
  62. 1 initializer :initialize_dependency_mechanism, group: :all do
  63. 1 ActiveSupport::Dependencies.mechanism = config.cache_classes ? :require : :load
  64. end
  65. 1 initializer :bootstrap_hook, group: :all do |app|
  66. 1 ActiveSupport.run_load_hooks(:before_initialize, app)
  67. end
  68. 1 initializer :set_secrets_root, group: :all do
  69. 1 Rails::Secrets.root = root
  70. end
  71. end
  72. end
  73. end

target/rubygems/gems/railties-5.2.3/lib/rails/application/default_middleware_stack.rb

78.95% lines covered

57 relevant lines. 45 lines covered and 12 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Rails
  3. 1 class Application
  4. 1 class DefaultMiddlewareStack
  5. 1 attr_reader :config, :paths, :app
  6. 1 def initialize(app, config, paths)
  7. 1 @app = app
  8. 1 @config = config
  9. 1 @paths = paths
  10. end
  11. 1 def build_stack
  12. 1 ActionDispatch::MiddlewareStack.new do |middleware|
  13. 1 if config.force_ssl
  14. middleware.use ::ActionDispatch::SSL, config.ssl_options
  15. end
  16. 1 middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header
  17. 1 if config.public_file_server.enabled
  18. 1 headers = config.public_file_server.headers || {}
  19. 1 middleware.use ::ActionDispatch::Static, paths["public"].first, index: config.public_file_server.index_name, headers: headers
  20. end
  21. 1 if rack_cache = load_rack_cache
  22. require "action_dispatch/http/rack_cache"
  23. middleware.use ::Rack::Cache, rack_cache
  24. end
  25. 1 if config.allow_concurrency == false
  26. # User has explicitly opted out of concurrent request
  27. # handling: presumably their code is not threadsafe
  28. middleware.use ::Rack::Lock
  29. end
  30. 1 middleware.use ::ActionDispatch::Executor, app.executor
  31. 1 middleware.use ::Rack::Runtime
  32. 1 middleware.use ::Rack::MethodOverride unless config.api_only
  33. 1 middleware.use ::ActionDispatch::RequestId
  34. 1 middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies
  35. 1 middleware.use ::Rails::Rack::Logger, config.log_tags
  36. 1 middleware.use ::ActionDispatch::ShowExceptions, show_exceptions_app
  37. 1 middleware.use ::ActionDispatch::DebugExceptions, app, config.debug_exception_response_format
  38. 1 unless config.cache_classes
  39. 1 middleware.use ::ActionDispatch::Reloader, app.reloader
  40. end
  41. 1 middleware.use ::ActionDispatch::Callbacks
  42. 1 middleware.use ::ActionDispatch::Cookies unless config.api_only
  43. 1 if !config.api_only && config.session_store
  44. 1 if config.force_ssl && config.ssl_options.fetch(:secure_cookies, true) && !config.session_options.key?(:secure)
  45. config.session_options[:secure] = true
  46. end
  47. 1 middleware.use config.session_store, config.session_options
  48. 1 middleware.use ::ActionDispatch::Flash
  49. end
  50. 1 unless config.api_only
  51. 1 middleware.use ::ActionDispatch::ContentSecurityPolicy::Middleware
  52. end
  53. 1 middleware.use ::Rack::Head
  54. 1 middleware.use ::Rack::ConditionalGet
  55. 1 middleware.use ::Rack::ETag, "no-cache"
  56. 1 middleware.use ::Rack::TempfileReaper unless config.api_only
  57. end
  58. end
  59. 1 private
  60. 1 def load_rack_cache
  61. 1 rack_cache = config.action_dispatch.rack_cache
  62. 1 return unless rack_cache
  63. begin
  64. require "rack/cache"
  65. rescue LoadError => error
  66. error.message << " Be sure to add rack-cache to your Gemfile"
  67. raise
  68. end
  69. if rack_cache == true
  70. {
  71. metastore: "rails:/",
  72. entitystore: "rails:/",
  73. verbose: false
  74. }
  75. else
  76. rack_cache
  77. end
  78. end
  79. 1 def show_exceptions_app
  80. 1 config.exceptions_app || ActionDispatch::PublicExceptions.new(Rails.public_path)
  81. end
  82. end
  83. end
  84. end

target/rubygems/gems/railties-5.2.3/lib/rails/application/finisher.rb

73.26% lines covered

86 relevant lines. 63 lines covered and 23 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Rails
  3. 1 class Application
  4. 1 module Finisher
  5. 1 include Initializable
  6. 1 initializer :add_generator_templates do
  7. 1 config.generators.templates.unshift(*paths["lib/templates"].existent)
  8. end
  9. 1 initializer :ensure_autoload_once_paths_as_subset do
  10. 2 extra = ActiveSupport::Dependencies.autoload_once_paths -
  11. ActiveSupport::Dependencies.autoload_paths
  12. 1 unless extra.empty?
  13. abort <<-end_error
  14. autoload_once_paths must be a subset of the autoload_paths.
  15. Extra items in autoload_once_paths: #{extra * ','}
  16. end_error
  17. end
  18. end
  19. 1 initializer :add_builtin_route do |app|
  20. 1 if Rails.env.development?
  21. 1 app.routes.prepend do
  22. 1 get "/rails/info/properties" => "rails/info#properties", internal: true
  23. 1 get "/rails/info/routes" => "rails/info#routes", internal: true
  24. 1 get "/rails/info" => "rails/info#index", internal: true
  25. end
  26. 1 app.routes.append do
  27. 1 get "/" => "rails/welcome#index", internal: true
  28. end
  29. end
  30. end
  31. # Setup default session store if not already set in config/application.rb
  32. 1 initializer :setup_default_session_store, before: :build_middleware_stack do |app|
  33. 1 unless app.config.session_store?
  34. 1 app_name = app.class.name ? app.railtie_name.chomp("_application") : ""
  35. 1 app.config.session_store :cookie_store, key: "_#{app_name}_session"
  36. end
  37. end
  38. 1 initializer :build_middleware_stack do
  39. 1 build_middleware_stack
  40. end
  41. 1 initializer :define_main_app_helper do |app|
  42. 1 app.routes.define_mounted_helper(:main_app)
  43. end
  44. 1 initializer :add_to_prepare_blocks do |app|
  45. 1 config.to_prepare_blocks.each do |block|
  46. app.reloader.to_prepare(&block)
  47. end
  48. end
  49. # This needs to happen before eager load so it happens
  50. # in exactly the same point regardless of config.eager_load
  51. 1 initializer :run_prepare_callbacks do |app|
  52. 1 app.reloader.prepare!
  53. end
  54. 1 initializer :eager_load! do
  55. 1 if config.eager_load
  56. ActiveSupport.run_load_hooks(:before_eager_load, self)
  57. config.eager_load_namespaces.each(&:eager_load!)
  58. end
  59. end
  60. # All initialization is done, including eager loading in production
  61. 1 initializer :finisher_hook do
  62. 1 ActiveSupport.run_load_hooks(:after_initialize, self)
  63. end
  64. 1 class MutexHook
  65. 1 def initialize(mutex = Mutex.new)
  66. @mutex = mutex
  67. end
  68. 1 def run
  69. @mutex.lock
  70. end
  71. 1 def complete(_state)
  72. @mutex.unlock
  73. end
  74. end
  75. 1 module InterlockHook
  76. 1 def self.run
  77. 2 ActiveSupport::Dependencies.interlock.start_running
  78. end
  79. 1 def self.complete(_state)
  80. 2 ActiveSupport::Dependencies.interlock.done_running
  81. end
  82. end
  83. 1 initializer :configure_executor_for_concurrency do |app|
  84. 1 if config.allow_concurrency == false
  85. # User has explicitly opted out of concurrent request
  86. # handling: presumably their code is not threadsafe
  87. app.executor.register_hook(MutexHook.new, outer: true)
  88. elsif config.allow_concurrency == :unsafe
  89. # Do nothing, even if we know this is dangerous. This is the
  90. # historical behavior for true.
  91. else
  92. # Default concurrency setting: enabled, but safe
  93. 1 unless config.cache_classes && config.eager_load
  94. # Without cache_classes + eager_load, the load interlock
  95. # is required for proper operation
  96. 1 app.executor.register_hook(InterlockHook, outer: true)
  97. end
  98. end
  99. end
  100. # Set routes reload after the finisher hook to ensure routes added in
  101. # the hook are taken into account.
  102. 1 initializer :set_routes_reloader_hook do |app|
  103. 1 reloader = routes_reloader
  104. 1 reloader.eager_load = app.config.eager_load
  105. 1 reloader.execute
  106. 1 reloaders << reloader
  107. 1 app.reloader.to_run do
  108. # We configure #execute rather than #execute_if_updated because if
  109. # autoloaded constants are cleared we need to reload routes also in
  110. # case any was used there, as in
  111. #
  112. # mount MailPreview => 'mail_view'
  113. #
  114. # This means routes are also reloaded if i18n is updated, which
  115. # might not be necessary, but in order to be more precise we need
  116. # some sort of reloaders dependency support, to be added.
  117. require_unload_lock!
  118. reloader.execute
  119. end
  120. end
  121. # Set clearing dependencies after the finisher hook to ensure paths
  122. # added in the hook are taken into account.
  123. 1 initializer :set_clear_dependencies_hook, group: :all do |app|
  124. 1 callback = lambda do
  125. ActiveSupport::DescendantsTracker.clear
  126. ActiveSupport::Dependencies.clear
  127. end
  128. 1 if config.cache_classes
  129. app.reloader.check = lambda { false }
  130. elsif config.reload_classes_only_on_change
  131. 1 app.reloader.check = lambda do
  132. 2 app.reloaders.map(&:updated?).any?
  133. end
  134. else
  135. app.reloader.check = lambda { true }
  136. end
  137. 1 if config.reload_classes_only_on_change
  138. 1 reloader = config.file_watcher.new(*watchable_args, &callback)
  139. 1 reloaders << reloader
  140. # Prepend this callback to have autoloaded constants cleared before
  141. # any other possible reloading, in case they need to autoload fresh
  142. # constants.
  143. 1 app.reloader.to_run(prepend: true) do
  144. # In addition to changes detected by the file watcher, if routes
  145. # or i18n have been updated we also need to clear constants,
  146. # that's why we run #execute rather than #execute_if_updated, this
  147. # callback has to clear autoloaded constants after any update.
  148. class_unload! do
  149. reloader.execute
  150. end
  151. end
  152. else
  153. app.reloader.to_complete do
  154. class_unload!(&callback)
  155. end
  156. end
  157. end
  158. # Disable dependency loading during request cycle
  159. 1 initializer :disable_dependency_loading do
  160. 1 if config.eager_load && config.cache_classes && !config.enable_dependency_loading
  161. ActiveSupport::Dependencies.unhook!
  162. end
  163. end
  164. end
  165. end
  166. end

target/rubygems/gems/railties-5.2.3/lib/rails/application/routes_reloader.rb

100.0% lines covered

31 relevant lines. 31 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/module/delegation"
  3. 1 module Rails
  4. 1 class Application
  5. 1 class RoutesReloader
  6. 1 attr_reader :route_sets, :paths
  7. 1 attr_accessor :eager_load
  8. 1 delegate :execute_if_updated, :execute, :updated?, to: :updater
  9. 1 def initialize
  10. 1 @paths = []
  11. 1 @route_sets = []
  12. 1 @eager_load = false
  13. end
  14. 1 def reload!
  15. 1 clear!
  16. 1 load_paths
  17. 1 finalize!
  18. 1 route_sets.each(&:eager_load!) if eager_load
  19. ensure
  20. 1 revert
  21. end
  22. 1 private
  23. 1 def updater
  24. 4 @updater ||= ActiveSupport::FileUpdateChecker.new(paths) { reload! }
  25. end
  26. 1 def clear!
  27. 1 route_sets.each do |routes|
  28. 2 routes.disable_clear_and_finalize = true
  29. 2 routes.clear!
  30. end
  31. end
  32. 1 def load_paths
  33. 3 paths.each { |path| load(path) }
  34. end
  35. 1 def finalize!
  36. 1 route_sets.each(&:finalize!)
  37. end
  38. 1 def revert
  39. 1 route_sets.each do |routes|
  40. 2 routes.disable_clear_and_finalize = false
  41. end
  42. end
  43. end
  44. end
  45. end

target/rubygems/gems/railties-5.2.3/lib/rails/backtrace_cleaner.rb

100.0% lines covered

23 relevant lines. 23 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/backtrace_cleaner"
  3. 1 module Rails
  4. 1 class BacktraceCleaner < ActiveSupport::BacktraceCleaner
  5. 1 APP_DIRS_PATTERN = /^\/?(app|config|lib|test|\(\w*\))/
  6. 1 RENDER_TEMPLATE_PATTERN = /:in `_render_template_\w*'/
  7. 1 EMPTY_STRING = "".freeze
  8. 1 SLASH = "/".freeze
  9. 1 DOT_SLASH = "./".freeze
  10. 1 def initialize
  11. 1 super
  12. 1 @root = "#{Rails.root}/".freeze
  13. 1 add_filter { |line| line.sub(@root, EMPTY_STRING) }
  14. 1 add_filter { |line| line.sub(RENDER_TEMPLATE_PATTERN, EMPTY_STRING) }
  15. 1 add_filter { |line| line.sub(DOT_SLASH, SLASH) } # for tests
  16. 1 add_gem_filters
  17. 1 add_silencer { |line| !APP_DIRS_PATTERN.match?(line) }
  18. end
  19. 1 private
  20. 1 def add_gem_filters
  21. 6 gems_paths = (Gem.path | [Gem.default_dir]).map { |p| Regexp.escape(p) }
  22. 1 return if gems_paths.empty?
  23. 1 gems_regexp = %r{(#{gems_paths.join('|')})/gems/([^/]+)-([\w.]+)/(.*)}
  24. 1 gems_result = '\2 (\3) \4'.freeze
  25. 1 add_filter { |line| line.sub(gems_regexp, gems_result) }
  26. end
  27. end
  28. end

target/rubygems/gems/railties-5.2.3/lib/rails/generators/test_case.rb

100.0% lines covered

13 relevant lines. 13 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "rails/generators"
  3. 1 require "rails/generators/testing/behaviour"
  4. 1 require "rails/generators/testing/setup_and_teardown"
  5. 1 require "rails/generators/testing/assertions"
  6. 1 require "fileutils"
  7. 1 module Rails
  8. 1 module Generators
  9. # Disable color in output. Easier to debug.
  10. 1 no_color!
  11. # This class provides a TestCase for testing generators. To setup, you need
  12. # just to configure the destination and set which generator is being tested:
  13. #
  14. # class AppGeneratorTest < Rails::Generators::TestCase
  15. # tests AppGenerator
  16. # destination File.expand_path("../tmp", __dir__)
  17. # end
  18. #
  19. # If you want to ensure your destination root is clean before running each test,
  20. # you can set a setup callback:
  21. #
  22. # class AppGeneratorTest < Rails::Generators::TestCase
  23. # tests AppGenerator
  24. # destination File.expand_path("../tmp", __dir__)
  25. # setup :prepare_destination
  26. # end
  27. 1 class TestCase < ActiveSupport::TestCase
  28. 1 include Rails::Generators::Testing::Behaviour
  29. 1 include Rails::Generators::Testing::SetupAndTeardown
  30. 1 include Rails::Generators::Testing::Assertions
  31. 1 include FileUtils
  32. end
  33. end
  34. end

target/rubygems/gems/railties-5.2.3/lib/rails/generators/testing/assertions.rb

40.54% lines covered

37 relevant lines. 15 lines covered and 22 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Rails
  3. 1 module Generators
  4. 1 module Testing
  5. 1 module Assertions
  6. # Asserts a given file exists. You need to supply an absolute path or a path relative
  7. # to the configured destination:
  8. #
  9. # assert_file "config/environment.rb"
  10. #
  11. # You can also give extra arguments. If the argument is a regexp, it will check if the
  12. # regular expression matches the given file content. If it's a string, it compares the
  13. # file with the given string:
  14. #
  15. # assert_file "config/environment.rb", /initialize/
  16. #
  17. # Finally, when a block is given, it yields the file content:
  18. #
  19. # assert_file "app/controllers/products_controller.rb" do |controller|
  20. # assert_instance_method :index, controller do |index|
  21. # assert_match(/Product\.all/, index)
  22. # end
  23. # end
  24. 1 def assert_file(relative, *contents)
  25. absolute = File.expand_path(relative, destination_root)
  26. assert File.exist?(absolute), "Expected file #{relative.inspect} to exist, but does not"
  27. read = File.read(absolute) if block_given? || !contents.empty?
  28. yield read if block_given?
  29. contents.each do |content|
  30. case content
  31. when String
  32. assert_equal content, read
  33. when Regexp
  34. assert_match content, read
  35. end
  36. end
  37. end
  38. 1 alias :assert_directory :assert_file
  39. # Asserts a given file does not exist. You need to supply an absolute path or a
  40. # path relative to the configured destination:
  41. #
  42. # assert_no_file "config/random.rb"
  43. 1 def assert_no_file(relative)
  44. absolute = File.expand_path(relative, destination_root)
  45. assert !File.exist?(absolute), "Expected file #{relative.inspect} to not exist, but does"
  46. end
  47. 1 alias :assert_no_directory :assert_no_file
  48. # Asserts a given migration exists. You need to supply an absolute path or a
  49. # path relative to the configured destination:
  50. #
  51. # assert_migration "db/migrate/create_products.rb"
  52. #
  53. # This method manipulates the given path and tries to find any migration which
  54. # matches the migration name. For example, the call above is converted to:
  55. #
  56. # assert_file "db/migrate/003_create_products.rb"
  57. #
  58. # Consequently, assert_migration accepts the same arguments has assert_file.
  59. 1 def assert_migration(relative, *contents, &block)
  60. file_name = migration_file_name(relative)
  61. assert file_name, "Expected migration #{relative} to exist, but was not found"
  62. assert_file file_name, *contents, &block
  63. end
  64. # Asserts a given migration does not exist. You need to supply an absolute path or a
  65. # path relative to the configured destination:
  66. #
  67. # assert_no_migration "db/migrate/create_products.rb"
  68. 1 def assert_no_migration(relative)
  69. file_name = migration_file_name(relative)
  70. assert_nil file_name, "Expected migration #{relative} to not exist, but found #{file_name}"
  71. end
  72. # Asserts the given class method exists in the given content. This method does not detect
  73. # class methods inside (class << self), only class methods which starts with "self.".
  74. # When a block is given, it yields the content of the method.
  75. #
  76. # assert_migration "db/migrate/create_products.rb" do |migration|
  77. # assert_class_method :up, migration do |up|
  78. # assert_match(/create_table/, up)
  79. # end
  80. # end
  81. 1 def assert_class_method(method, content, &block)
  82. assert_instance_method "self.#{method}", content, &block
  83. end
  84. # Asserts the given method exists in the given content. When a block is given,
  85. # it yields the content of the method.
  86. #
  87. # assert_file "app/controllers/products_controller.rb" do |controller|
  88. # assert_instance_method :index, controller do |index|
  89. # assert_match(/Product\.all/, index)
  90. # end
  91. # end
  92. 1 def assert_instance_method(method, content)
  93. assert content =~ /(\s+)def #{method}(\(.+\))?(.*?)\n\1end/m, "Expected to have method #{method}"
  94. yield $3.strip if block_given?
  95. end
  96. 1 alias :assert_method :assert_instance_method
  97. # Asserts the given attribute type gets translated to a field type
  98. # properly:
  99. #
  100. # assert_field_type :date, :date_select
  101. 1 def assert_field_type(attribute_type, field_type)
  102. assert_equal(field_type, create_generated_attribute(attribute_type).field_type)
  103. end
  104. # Asserts the given attribute type gets a proper default value:
  105. #
  106. # assert_field_default_value :string, "MyString"
  107. 1 def assert_field_default_value(attribute_type, value)
  108. if value.nil?
  109. assert_nil(create_generated_attribute(attribute_type).default)
  110. else
  111. assert_equal(value, create_generated_attribute(attribute_type).default)
  112. end
  113. end
  114. end
  115. end
  116. end
  117. end

target/rubygems/gems/railties-5.2.3/lib/rails/generators/testing/behaviour.rb

66.67% lines covered

45 relevant lines. 30 lines covered and 15 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/class/attribute"
  3. 1 require "active_support/core_ext/module/delegation"
  4. 1 require "active_support/core_ext/hash/reverse_merge"
  5. 1 require "active_support/core_ext/kernel/reporting"
  6. 1 require "active_support/testing/stream"
  7. 1 require "active_support/concern"
  8. 1 require "rails/generators"
  9. 1 module Rails
  10. 1 module Generators
  11. 1 module Testing
  12. 1 module Behaviour
  13. 1 extend ActiveSupport::Concern
  14. 1 include ActiveSupport::Testing::Stream
  15. 1 included do
  16. # Generators frequently change the current path using +FileUtils.cd+.
  17. # So we need to store the path at file load and revert back to it after each test.
  18. 1 class_attribute :current_path, default: File.expand_path(Dir.pwd)
  19. 1 class_attribute :default_arguments, default: []
  20. 1 class_attribute :destination_root
  21. 1 class_attribute :generator_class
  22. end
  23. 1 module ClassMethods
  24. # Sets which generator should be tested:
  25. #
  26. # tests AppGenerator
  27. 1 def tests(klass)
  28. self.generator_class = klass
  29. end
  30. # Sets default arguments on generator invocation. This can be overwritten when
  31. # invoking it.
  32. #
  33. # arguments %w(app_name --skip-active-record)
  34. 1 def arguments(array)
  35. self.default_arguments = array
  36. end
  37. # Sets the destination of generator files:
  38. #
  39. # destination File.expand_path("../tmp", __dir__)
  40. 1 def destination(path)
  41. self.destination_root = path
  42. end
  43. end
  44. # Runs the generator configured for this class. The first argument is an array like
  45. # command line arguments:
  46. #
  47. # class AppGeneratorTest < Rails::Generators::TestCase
  48. # tests AppGenerator
  49. # destination File.expand_path("../tmp", __dir__)
  50. # setup :prepare_destination
  51. #
  52. # test "database.yml is not created when skipping Active Record" do
  53. # run_generator %w(myapp --skip-active-record)
  54. # assert_no_file "config/database.yml"
  55. # end
  56. # end
  57. #
  58. # You can provide a configuration hash as second argument. This method returns the output
  59. # printed by the generator.
  60. 1 def run_generator(args = default_arguments, config = {})
  61. capture(:stdout) do
  62. args += ["--skip-bundle"] unless args.include? "--dev"
  63. generator_class.start(args, config.reverse_merge(destination_root: destination_root))
  64. end
  65. end
  66. # Instantiate the generator.
  67. 1 def generator(args = default_arguments, options = {}, config = {})
  68. @generator ||= generator_class.new(args, options, config.reverse_merge(destination_root: destination_root))
  69. end
  70. # Create a Rails::Generators::GeneratedAttribute by supplying the
  71. # attribute type and, optionally, the attribute name:
  72. #
  73. # create_generated_attribute(:string, 'name')
  74. 1 def create_generated_attribute(attribute_type, name = "test", index = nil)
  75. Rails::Generators::GeneratedAttribute.parse([name, attribute_type, index].compact.join(":"))
  76. end
  77. 1 private
  78. 1 def destination_root_is_set?
  79. raise "You need to configure your Rails::Generators::TestCase destination root." unless destination_root
  80. end
  81. 1 def ensure_current_path
  82. cd current_path
  83. end
  84. # Clears all files and directories in destination.
  85. 1 def prepare_destination # :doc:
  86. rm_rf(destination_root)
  87. mkdir_p(destination_root)
  88. end
  89. 1 def migration_file_name(relative)
  90. absolute = File.expand_path(relative, destination_root)
  91. dirname, file_name = File.dirname(absolute), File.basename(absolute).sub(/\.rb$/, "")
  92. Dir.glob("#{dirname}/[0-9]*_*.rb").grep(/\d+_#{file_name}.rb$/).first
  93. end
  94. end
  95. end
  96. end
  97. end

target/rubygems/gems/railties-5.2.3/lib/rails/generators/testing/setup_and_teardown.rb

54.55% lines covered

11 relevant lines. 6 lines covered and 5 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Rails
  3. 1 module Generators
  4. 1 module Testing
  5. 1 module SetupAndTeardown
  6. 1 def setup # :nodoc:
  7. destination_root_is_set?
  8. ensure_current_path
  9. super
  10. end
  11. 1 def teardown # :nodoc:
  12. ensure_current_path
  13. super
  14. end
  15. end
  16. end
  17. end
  18. end

target/rubygems/gems/railties-5.2.3/lib/rails/rack/logger.rb

84.09% lines covered

44 relevant lines. 37 lines covered and 7 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/time/conversions"
  3. 1 require "active_support/core_ext/object/blank"
  4. 1 require "active_support/log_subscriber"
  5. 1 require "action_dispatch/http/request"
  6. 1 require "rack/body_proxy"
  7. 1 module Rails
  8. 1 module Rack
  9. # Sets log tags, logs the request, calls the app, and flushes the logs.
  10. #
  11. # Log tags (+taggers+) can be an Array containing: methods that the +request+
  12. # object responds to, objects that respond to +to_s+ or Proc objects that accept
  13. # an instance of the +request+ object.
  14. 1 class Logger < ActiveSupport::LogSubscriber
  15. 1 def initialize(app, taggers = nil)
  16. 1 @app = app
  17. 1 @taggers = taggers || []
  18. end
  19. 1 def call(env)
  20. 2 request = ActionDispatch::Request.new(env)
  21. 2 if logger.respond_to?(:tagged)
  22. 4 logger.tagged(compute_tags(request)) { call_app(request, env) }
  23. else
  24. call_app(request, env)
  25. end
  26. end
  27. 1 private
  28. 1 def call_app(request, env) # :doc:
  29. 2 instrumenter = ActiveSupport::Notifications.instrumenter
  30. 2 instrumenter.start "request.action_dispatch", request: request
  31. 4 logger.info { started_request_message(request) }
  32. 2 status, headers, body = @app.call(env)
  33. 4 body = ::Rack::BodyProxy.new(body) { finish(request) }
  34. 2 [status, headers, body]
  35. rescue Exception
  36. finish(request)
  37. raise
  38. ensure
  39. 2 ActiveSupport::LogSubscriber.flush_all!
  40. end
  41. # Started GET "/session/new" for 127.0.0.1 at 2012-09-26 14:51:42 -0700
  42. 1 def started_request_message(request) # :doc:
  43. 2 'Started %s "%s" for %s at %s' % [
  44. 1 request.request_method,
  45. 1 request.filtered_path,
  46. 1 request.remote_ip,
  47. 1 Time.now.to_default_s ]
  48. end
  49. 1 def compute_tags(request) # :doc:
  50. 2 @taggers.collect do |tag|
  51. case tag
  52. when Proc
  53. tag.call(request)
  54. when Symbol
  55. request.send(tag)
  56. else
  57. tag
  58. end
  59. end
  60. end
  61. 1 def finish(request)
  62. 2 instrumenter = ActiveSupport::Notifications.instrumenter
  63. 2 instrumenter.finish "request.action_dispatch", request: request
  64. end
  65. 1 def logger
  66. 6 Rails.logger
  67. end
  68. end
  69. end
  70. end

target/rubygems/gems/railties-5.2.3/lib/rails/test_help.rb

84.62% lines covered

26 relevant lines. 22 lines covered and 4 lines missed.
    
  1. # frozen_string_literal: true
  2. # Make double-sure the RAILS_ENV is not set to production,
  3. # so fixtures aren't loaded into that environment
  4. 1 abort("Abort testing: Your Rails environment is running in production mode!") if Rails.env.production?
  5. 1 require "active_support/test_case"
  6. 1 require "action_controller"
  7. 1 require "action_controller/test_case"
  8. 1 require "action_dispatch/testing/integration"
  9. 1 require "rails/generators/test_case"
  10. 1 require "active_support/testing/autorun"
  11. 1 if defined?(ActiveRecord::Base)
  12. 1 begin
  13. 1 ActiveRecord::Migration.maintain_test_schema!
  14. rescue ActiveRecord::PendingMigrationError => e
  15. puts e.to_s.strip
  16. exit 1
  17. end
  18. 1 module ActiveSupport
  19. 1 class TestCase
  20. 1 include ActiveRecord::TestFixtures
  21. 1 self.fixture_path = "#{Rails.root}/test/fixtures/"
  22. 1 self.file_fixture_path = fixture_path + "files"
  23. end
  24. end
  25. 1 ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path
  26. end
  27. # :enddoc:
  28. 1 class ActionController::TestCase
  29. 1 def before_setup # :nodoc:
  30. @routes = Rails.application.routes
  31. super
  32. end
  33. end
  34. 1 class ActionDispatch::IntegrationTest
  35. 1 def before_setup # :nodoc:
  36. 2 @routes = Rails.application.routes
  37. 2 super
  38. end
  39. end

target/rubygems/gems/railties-5.2.3/lib/rails/test_unit/reporter.rb

48.39% lines covered

62 relevant lines. 30 lines covered and 32 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "active_support/core_ext/class/attribute"
  3. 1 require "minitest"
  4. 1 module Rails
  5. 1 class TestUnitReporter < Minitest::StatisticsReporter
  6. 1 class_attribute :executable, default: "bin/rails test"
  7. 1 def record(result)
  8. 2 super
  9. 2 if options[:verbose]
  10. io.puts color_output(format_line(result), by: result)
  11. else
  12. 2 io.print color_output(result.result_code, by: result)
  13. end
  14. 2 if output_inline? && result.failure && (!result.skipped? || options[:verbose])
  15. io.puts
  16. io.puts
  17. io.puts color_output(result, by: result)
  18. io.puts
  19. io.puts format_rerun_snippet(result)
  20. io.puts
  21. end
  22. 2 if fail_fast? && result.failure && !result.skipped?
  23. raise Interrupt
  24. end
  25. end
  26. 1 def report
  27. 1 return if output_inline? || filtered_results.empty?
  28. io.puts
  29. io.puts "Failed tests:"
  30. io.puts
  31. io.puts aggregated_results
  32. end
  33. 1 def aggregated_results # :nodoc:
  34. filtered_results.map { |result| format_rerun_snippet(result) }.join "\n"
  35. end
  36. 1 def filtered_results
  37. if options[:verbose]
  38. results
  39. else
  40. results.reject(&:skipped?)
  41. end
  42. end
  43. 1 def relative_path_for(file)
  44. file.sub(/^#{app_root}\/?/, "")
  45. end
  46. 1 private
  47. 1 def output_inline?
  48. 3 options[:output_inline]
  49. end
  50. 1 def fail_fast?
  51. 2 options[:fail_fast]
  52. end
  53. 1 def format_line(result)
  54. klass = result.respond_to?(:klass) ? result.klass : result.class
  55. "%s#%s = %.2f s = %s" % [klass, result.name, result.time, result.result_code]
  56. end
  57. 1 def format_rerun_snippet(result)
  58. location, line = if result.respond_to?(:source_location)
  59. result.source_location
  60. else
  61. result.method(result.name).source_location
  62. end
  63. "#{executable} #{relative_path_for(location)}:#{line}"
  64. end
  65. 1 def app_root
  66. @app_root ||=
  67. if defined?(ENGINE_ROOT)
  68. ENGINE_ROOT
  69. elsif Rails.respond_to?(:root)
  70. Rails.root
  71. end
  72. end
  73. 1 def colored_output?
  74. 2 options[:color] && io.respond_to?(:tty?) && io.tty?
  75. end
  76. 1 codes = { red: 31, green: 32, yellow: 33 }
  77. COLOR_BY_RESULT_CODE = {
  78. "." => codes[:green],
  79. "E" => codes[:red],
  80. "F" => codes[:red],
  81. "S" => codes[:yellow]
  82. }
  83. 1 def color_output(string, by:)
  84. 2 if colored_output?
  85. "\e[#{COLOR_BY_RESULT_CODE[by.result_code]}m#{string}\e[0m"
  86. else
  87. 2 string
  88. end
  89. end
  90. end
  91. end

target/rubygems/gems/responders-3.0.0/lib/responders/flash_responder.rb

27.54% lines covered

69 relevant lines. 19 lines covered and 50 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Responders
  3. # Responder to automatically set flash messages based on I18n API. It checks for
  4. # message based on the current action, but also allows defaults to be set, using
  5. # the following order:
  6. #
  7. # flash.controller_name.action_name.status
  8. # flash.actions.action_name.status
  9. #
  10. # So, if you have a CarsController, create action, it will check for:
  11. #
  12. # flash.cars.create.status
  13. # flash.actions.create.status
  14. #
  15. # The statuses by default are :notice (when the object can be created, updated
  16. # or destroyed with success) and :alert (when the object cannot be created
  17. # or updated).
  18. #
  19. # On I18n, the resource_name given is available as interpolation option,
  20. # this means you can set:
  21. #
  22. # flash:
  23. # actions:
  24. # create:
  25. # notice: "Hooray! %{resource_name} was successfully created!"
  26. #
  27. # But sometimes, flash messages are not that simple. Going back
  28. # to cars example, you might want to say the brand of the car when it's
  29. # updated. Well, that's easy also:
  30. #
  31. # flash:
  32. # cars:
  33. # update:
  34. # notice: "Hooray! You just tuned your %{car_brand}!"
  35. #
  36. # Since :car_name is not available for interpolation by default, you have
  37. # to overwrite `flash_interpolation_options` in your controller.
  38. #
  39. # def flash_interpolation_options
  40. # { :car_brand => @car.brand }
  41. # end
  42. #
  43. # Then you will finally have:
  44. #
  45. # 'Hooray! You just tuned your Aston Martin!'
  46. #
  47. # If your controller is namespaced, for example Admin::CarsController,
  48. # the messages will be checked in the following order:
  49. #
  50. # flash.admin.cars.create.status
  51. # flash.admin.actions.create.status
  52. # flash.cars.create.status
  53. # flash.actions.create.status
  54. #
  55. # You can also have flash messages with embedded HTML. Just create a scope that
  56. # ends with <tt>_html</tt> as the scopes below:
  57. #
  58. # flash.actions.create.notice_html
  59. # flash.cars.create.notice_html
  60. #
  61. # == Options
  62. #
  63. # FlashResponder also accepts some options through respond_with API.
  64. #
  65. # * :flash - When set to false, no flash message is set.
  66. #
  67. # respond_with(@user, :flash => true)
  68. #
  69. # * :notice - Supply the message to be set if the record has no errors.
  70. # * :alert - Supply the message to be set if the record has errors.
  71. #
  72. # respond_with(@user, :notice => "Hooray! Welcome!", :alert => "Woot! You failed.")
  73. #
  74. # * :flash_now - Sets the flash message using flash.now. Accepts true, :on_failure or :on_sucess.
  75. #
  76. # == Configure status keys
  77. #
  78. # As said previously, FlashResponder by default use :notice and :alert
  79. # keys. You can change that by setting the status_keys:
  80. #
  81. # Responders::FlashResponder.flash_keys = [ :success, :failure ]
  82. #
  83. # However, the options :notice and :alert to respond_with are kept :notice
  84. # and :alert.
  85. #
  86. 1 module FlashResponder
  87. 1 class << self
  88. 1 attr_accessor :flash_keys, :namespace_lookup, :helper
  89. end
  90. 1 self.flash_keys = [ :notice, :alert ]
  91. 1 self.namespace_lookup = false
  92. 1 self.helper = Object.new.extend(
  93. ActionView::Helpers::TranslationHelper,
  94. ActionView::Helpers::TagHelper
  95. )
  96. 1 def initialize(controller, resources, options = {})
  97. super
  98. @flash = options.delete(:flash)
  99. @notice = options.delete(:notice)
  100. @alert = options.delete(:alert)
  101. @flash_now = options.delete(:flash_now) { :on_failure }
  102. end
  103. 1 def to_html
  104. set_flash_message! if set_flash_message?
  105. super
  106. end
  107. 1 def to_js
  108. set_flash_message! if set_flash_message?
  109. defined?(super) ? super : to_format
  110. end
  111. 1 protected
  112. 1 def set_flash_message!
  113. if has_errors?
  114. status = Responders::FlashResponder.flash_keys.last
  115. set_flash(status, @alert)
  116. else
  117. status = Responders::FlashResponder.flash_keys.first
  118. set_flash(status, @notice)
  119. end
  120. return if controller.flash[status].present?
  121. options = mount_i18n_options(status)
  122. message = Responders::FlashResponder.helper.t options[:default].shift, options
  123. set_flash(status, message)
  124. end
  125. 1 def set_flash(key, value)
  126. return if value.blank?
  127. flash = controller.flash
  128. flash = flash.now if set_flash_now?
  129. flash[key] ||= value
  130. end
  131. 1 def set_flash_now?
  132. @flash_now == true || format == :js ||
  133. (default_action && (has_errors? ? @flash_now == :on_failure : @flash_now == :on_success))
  134. end
  135. 1 def set_flash_message? #:nodoc:
  136. !get? && @flash != false
  137. end
  138. 1 def mount_i18n_options(status) #:nodoc:
  139. options = {
  140. default: flash_defaults_by_namespace(status),
  141. resource_name: resource_name,
  142. downcase_resource_name: resource_name.downcase
  143. }
  144. controller_options = controller_interpolation_options
  145. if controller_options
  146. options.merge!(controller_options)
  147. end
  148. options
  149. end
  150. 1 def controller_interpolation_options
  151. if controller.respond_to?(:flash_interpolation_options, true)
  152. controller.send(:flash_interpolation_options)
  153. elsif controller.respond_to?(:interpolation_options, true)
  154. ActiveSupport::Deprecation.warn("[responders] `#{controller.class}#interpolation_options` is deprecated, please rename it to `flash_interpolation_options`.")
  155. controller.send(:interpolation_options)
  156. end
  157. end
  158. 1 def resource_name
  159. if resource.class.respond_to?(:model_name)
  160. resource.class.model_name.human
  161. else
  162. resource.class.name.underscore.humanize
  163. end
  164. end
  165. 1 def flash_defaults_by_namespace(status) #:nodoc:
  166. defaults = []
  167. slices = controller.controller_path.split("/")
  168. lookup = Responders::FlashResponder.namespace_lookup
  169. begin
  170. controller_scope = :"flash.#{slices.fill(controller.controller_name, -1).join(".")}.#{controller.action_name}.#{status}"
  171. actions_scope = lookup ? slices.fill("actions", -1).join(".") : :actions
  172. actions_scope = :"flash.#{actions_scope}.#{controller.action_name}.#{status}"
  173. defaults << :"#{controller_scope}_html"
  174. defaults << controller_scope
  175. defaults << :"#{actions_scope}_html"
  176. defaults << actions_scope
  177. slices.shift
  178. end while slices.size > 0 && lookup
  179. defaults << ""
  180. end
  181. end
  182. end

target/rubygems/gems/sprockets-3.7.2/lib/sprockets/autoload/sass.rb

100.0% lines covered

4 relevant lines. 4 lines covered and 0 lines missed.
    
  1. 1 require 'sass'
  2. 1 module Sprockets
  3. 1 module Autoload
  4. 1 Sass = ::Sass
  5. end
  6. end

target/rubygems/gems/sprockets-3.7.2/lib/sprockets/cache/file_store.rb

31.65% lines covered

79 relevant lines. 25 lines covered and 54 lines missed.
    
  1. 1 require 'fileutils'
  2. 1 require 'logger'
  3. 1 require 'sprockets/encoding_utils'
  4. 1 require 'sprockets/path_utils'
  5. 1 require 'zlib'
  6. 1 module Sprockets
  7. 1 class Cache
  8. # Public: A file system cache store that automatically cleans up old keys.
  9. #
  10. # Assign the instance to the Environment#cache.
  11. #
  12. # environment.cache = Sprockets::Cache::FileStore.new("/tmp")
  13. #
  14. # See Also
  15. #
  16. # ActiveSupport::Cache::FileStore
  17. #
  18. 1 class FileStore
  19. # Internal: Default key limit for store.
  20. 1 DEFAULT_MAX_SIZE = 25 * 1024 * 1024
  21. # Internal: Default standard error fatal logger.
  22. #
  23. # Returns a Logger.
  24. 1 def self.default_logger
  25. logger = Logger.new($stderr)
  26. logger.level = Logger::FATAL
  27. logger
  28. end
  29. # Public: Initialize the cache store.
  30. #
  31. # root - A String path to a directory to persist cached values to.
  32. # max_size - A Integer of the maximum number of keys the store will hold.
  33. # (default: 1000).
  34. 1 def initialize(root, max_size = DEFAULT_MAX_SIZE, logger = self.class.default_logger)
  35. 1 @root = root
  36. 1 @max_size = max_size
  37. 1 @gc_size = max_size * 0.75
  38. 1 @logger = logger
  39. end
  40. # Public: Retrieve value from cache.
  41. #
  42. # This API should not be used directly, but via the Cache wrapper API.
  43. #
  44. # key - String cache key.
  45. #
  46. # Returns Object or nil or the value is not set.
  47. 1 def get(key)
  48. path = File.join(@root, "#{key}.cache")
  49. value = safe_open(path) do |f|
  50. begin
  51. EncodingUtils.unmarshaled_deflated(f.read, Zlib::MAX_WBITS)
  52. rescue Exception => e
  53. @logger.error do
  54. "#{self.class}[#{path}] could not be unmarshaled: " +
  55. "#{e.class}: #{e.message}"
  56. end
  57. nil
  58. end
  59. end
  60. if value
  61. FileUtils.touch(path)
  62. value
  63. end
  64. end
  65. # Public: Set a key and value in the cache.
  66. #
  67. # This API should not be used directly, but via the Cache wrapper API.
  68. #
  69. # key - String cache key.
  70. # value - Object value.
  71. #
  72. # Returns Object value.
  73. 1 def set(key, value)
  74. path = File.join(@root, "#{key}.cache")
  75. # Ensure directory exists
  76. FileUtils.mkdir_p File.dirname(path)
  77. # Check if cache exists before writing
  78. exists = File.exist?(path)
  79. # Serialize value
  80. marshaled = Marshal.dump(value)
  81. # Compress if larger than 4KB
  82. if marshaled.bytesize > 4 * 1024
  83. deflater = Zlib::Deflate.new(
  84. Zlib::BEST_COMPRESSION,
  85. Zlib::MAX_WBITS,
  86. Zlib::MAX_MEM_LEVEL,
  87. Zlib::DEFAULT_STRATEGY
  88. )
  89. deflater << marshaled
  90. raw = deflater.finish
  91. else
  92. raw = marshaled
  93. end
  94. # Write data
  95. PathUtils.atomic_write(path) do |f|
  96. f.write(raw)
  97. @size = size + f.size unless exists
  98. end
  99. # GC if necessary
  100. gc! if size > @max_size
  101. value
  102. end
  103. # Public: Pretty inspect
  104. #
  105. # Returns String.
  106. 1 def inspect
  107. "#<#{self.class} size=#{size}/#{@max_size}>"
  108. end
  109. 1 private
  110. # Internal: Get all cache files along with stats.
  111. #
  112. # Returns an Array of [String filename, File::Stat] pairs sorted by
  113. # mtime.
  114. 1 def find_caches
  115. Dir.glob(File.join(@root, '**/*.cache')).reduce([]) { |stats, filename|
  116. stat = safe_stat(filename)
  117. # stat maybe nil if file was removed between the time we called
  118. # dir.glob and the next stat
  119. stats << [filename, stat] if stat
  120. stats
  121. }.sort_by { |_, stat| stat.mtime.to_i }
  122. end
  123. 1 def size
  124. @size ||= compute_size(find_caches)
  125. end
  126. 1 def compute_size(caches)
  127. caches.inject(0) { |sum, (_, stat)| sum + stat.size }
  128. end
  129. 1 def safe_stat(fn)
  130. File.stat(fn)
  131. rescue Errno::ENOENT
  132. nil
  133. end
  134. 1 def safe_open(path, &block)
  135. if File.exist?(path)
  136. File.open(path, 'rb', &block)
  137. end
  138. rescue Errno::ENOENT
  139. end
  140. 1 def gc!
  141. start_time = Time.now
  142. caches = find_caches
  143. size = compute_size(caches)
  144. delete_caches, keep_caches = caches.partition { |filename, stat|
  145. deleted = size > @gc_size
  146. size -= stat.size
  147. deleted
  148. }
  149. return if delete_caches.empty?
  150. FileUtils.remove(delete_caches.map(&:first), force: true)
  151. @size = compute_size(keep_caches)
  152. @logger.warn do
  153. secs = Time.now.to_f - start_time.to_f
  154. "#{self.class}[#{@root}] garbage collected " +
  155. "#{delete_caches.size} files (#{(secs * 1000).to_i}ms)"
  156. end
  157. end
  158. end
  159. end
  160. end

target/rubygems/gems/thor-0.20.3/lib/thor/shell/basic.rb

20.83% lines covered

216 relevant lines. 45 lines covered and 171 lines missed.
    
  1. 1 class Thor
  2. 1 module Shell
  3. 1 class Basic
  4. 1 DEFAULT_TERMINAL_WIDTH = 80
  5. 1 attr_accessor :base
  6. 1 attr_reader :padding
  7. # Initialize base, mute and padding to nil.
  8. #
  9. 1 def initialize #:nodoc:
  10. @base = nil
  11. @mute = false
  12. @padding = 0
  13. @always_force = false
  14. end
  15. # Mute everything that's inside given block
  16. #
  17. 1 def mute
  18. @mute = true
  19. yield
  20. ensure
  21. @mute = false
  22. end
  23. # Check if base is muted
  24. #
  25. 1 def mute?
  26. @mute
  27. end
  28. # Sets the output padding, not allowing less than zero values.
  29. #
  30. 1 def padding=(value)
  31. @padding = [0, value].max
  32. end
  33. # Sets the output padding while executing a block and resets it.
  34. #
  35. 1 def indent(count = 1)
  36. orig_padding = padding
  37. self.padding = padding + count
  38. yield
  39. self.padding = orig_padding
  40. end
  41. # Asks something to the user and receives a response.
  42. #
  43. # If a default value is specified it will be presented to the user
  44. # and allows them to select that value with an empty response. This
  45. # option is ignored when limited answers are supplied.
  46. #
  47. # If asked to limit the correct responses, you can pass in an
  48. # array of acceptable answers. If one of those is not supplied,
  49. # they will be shown a message stating that one of those answers
  50. # must be given and re-asked the question.
  51. #
  52. # If asking for sensitive information, the :echo option can be set
  53. # to false to mask user input from $stdin.
  54. #
  55. # If the required input is a path, then set the path option to
  56. # true. This will enable tab completion for file paths relative
  57. # to the current working directory on systems that support
  58. # Readline.
  59. #
  60. # ==== Example
  61. # ask("What is your name?")
  62. #
  63. # ask("What is the planet furthest from the sun?", :default => "Pluto")
  64. #
  65. # ask("What is your favorite Neopolitan flavor?", :limited_to => ["strawberry", "chocolate", "vanilla"])
  66. #
  67. # ask("What is your password?", :echo => false)
  68. #
  69. # ask("Where should the file be saved?", :path => true)
  70. #
  71. 1 def ask(statement, *args)
  72. options = args.last.is_a?(Hash) ? args.pop : {}
  73. color = args.first
  74. if options[:limited_to]
  75. ask_filtered(statement, color, options)
  76. else
  77. ask_simply(statement, color, options)
  78. end
  79. end
  80. # Say (print) something to the user. If the sentence ends with a whitespace
  81. # or tab character, a new line is not appended (print + flush). Otherwise
  82. # are passed straight to puts (behavior got from Highline).
  83. #
  84. # ==== Example
  85. # say("I know you knew that.")
  86. #
  87. 1 def say(message = "", color = nil, force_new_line = (message.to_s !~ /( |\t)\Z/))
  88. buffer = prepare_message(message, *color)
  89. buffer << "\n" if force_new_line && !message.to_s.end_with?("\n")
  90. stdout.print(buffer)
  91. stdout.flush
  92. end
  93. # Say a status with the given color and appends the message. Since this
  94. # method is used frequently by actions, it allows nil or false to be given
  95. # in log_status, avoiding the message from being shown. If a Symbol is
  96. # given in log_status, it's used as the color.
  97. #
  98. 1 def say_status(status, message, log_status = true)
  99. return if quiet? || log_status == false
  100. spaces = " " * (padding + 1)
  101. color = log_status.is_a?(Symbol) ? log_status : :green
  102. status = status.to_s.rjust(12)
  103. status = set_color status, color, true if color
  104. buffer = "#{status}#{spaces}#{message}"
  105. buffer = "#{buffer}\n" unless buffer.end_with?("\n")
  106. stdout.print(buffer)
  107. stdout.flush
  108. end
  109. # Make a question the to user and returns true if the user replies "y" or
  110. # "yes".
  111. #
  112. 1 def yes?(statement, color = nil)
  113. !!(ask(statement, color, :add_to_history => false) =~ is?(:yes))
  114. end
  115. # Make a question the to user and returns true if the user replies "n" or
  116. # "no".
  117. #
  118. 1 def no?(statement, color = nil)
  119. !!(ask(statement, color, :add_to_history => false) =~ is?(:no))
  120. end
  121. # Prints values in columns
  122. #
  123. # ==== Parameters
  124. # Array[String, String, ...]
  125. #
  126. 1 def print_in_columns(array)
  127. return if array.empty?
  128. colwidth = (array.map { |el| el.to_s.size }.max || 0) + 2
  129. array.each_with_index do |value, index|
  130. # Don't output trailing spaces when printing the last column
  131. if ((((index + 1) % (terminal_width / colwidth))).zero? && !index.zero?) || index + 1 == array.length
  132. stdout.puts value
  133. else
  134. stdout.printf("%-#{colwidth}s", value)
  135. end
  136. end
  137. end
  138. # Prints a table.
  139. #
  140. # ==== Parameters
  141. # Array[Array[String, String, ...]]
  142. #
  143. # ==== Options
  144. # indent<Integer>:: Indent the first column by indent value.
  145. # colwidth<Integer>:: Force the first column to colwidth spaces wide.
  146. #
  147. 1 def print_table(array, options = {}) # rubocop:disable MethodLength
  148. return if array.empty?
  149. formats = []
  150. indent = options[:indent].to_i
  151. colwidth = options[:colwidth]
  152. options[:truncate] = terminal_width if options[:truncate] == true
  153. formats << "%-#{colwidth + 2}s".dup if colwidth
  154. start = colwidth ? 1 : 0
  155. colcount = array.max { |a, b| a.size <=> b.size }.size
  156. maximas = []
  157. start.upto(colcount - 1) do |index|
  158. maxima = array.map { |row| row[index] ? row[index].to_s.size : 0 }.max
  159. maximas << maxima
  160. formats << if index == colcount - 1
  161. # Don't output 2 trailing spaces when printing the last column
  162. "%-s".dup
  163. else
  164. "%-#{maxima + 2}s".dup
  165. end
  166. end
  167. formats[0] = formats[0].insert(0, " " * indent)
  168. formats << "%s"
  169. array.each do |row|
  170. sentence = "".dup
  171. row.each_with_index do |column, index|
  172. maxima = maximas[index]
  173. f = if column.is_a?(Numeric)
  174. if index == row.size - 1
  175. # Don't output 2 trailing spaces when printing the last column
  176. "%#{maxima}s"
  177. else
  178. "%#{maxima}s "
  179. end
  180. else
  181. formats[index]
  182. end
  183. sentence << f % column.to_s
  184. end
  185. sentence = truncate(sentence, options[:truncate]) if options[:truncate]
  186. stdout.puts sentence
  187. end
  188. end
  189. # Prints a long string, word-wrapping the text to the current width of the
  190. # terminal display. Ideal for printing heredocs.
  191. #
  192. # ==== Parameters
  193. # String
  194. #
  195. # ==== Options
  196. # indent<Integer>:: Indent each line of the printed paragraph by indent value.
  197. #
  198. 1 def print_wrapped(message, options = {})
  199. indent = options[:indent] || 0
  200. width = terminal_width - indent
  201. paras = message.split("\n\n")
  202. paras.map! do |unwrapped|
  203. counter = 0
  204. unwrapped.split(" ").inject do |memo, word|
  205. word = word.gsub(/\n\005/, "\n").gsub(/\005/, "\n")
  206. counter = 0 if word.include? "\n"
  207. if (counter + word.length + 1) < width
  208. memo = "#{memo} #{word}"
  209. counter += (word.length + 1)
  210. else
  211. memo = "#{memo}\n#{word}"
  212. counter = word.length
  213. end
  214. memo
  215. end
  216. end.compact!
  217. paras.each do |para|
  218. para.split("\n").each do |line|
  219. stdout.puts line.insert(0, " " * indent)
  220. end
  221. stdout.puts unless para == paras.last
  222. end
  223. end
  224. # Deals with file collision and returns true if the file should be
  225. # overwritten and false otherwise. If a block is given, it uses the block
  226. # response as the content for the diff.
  227. #
  228. # ==== Parameters
  229. # destination<String>:: the destination file to solve conflicts
  230. # block<Proc>:: an optional block that returns the value to be used in diff and merge
  231. #
  232. 1 def file_collision(destination)
  233. return true if @always_force
  234. options = block_given? ? "[Ynaqdhm]" : "[Ynaqh]"
  235. loop do
  236. answer = ask(
  237. %[Overwrite #{destination}? (enter "h" for help) #{options}],
  238. :add_to_history => false
  239. )
  240. case answer
  241. when nil
  242. say ""
  243. return true
  244. when is?(:yes), is?(:force), ""
  245. return true
  246. when is?(:no), is?(:skip)
  247. return false
  248. when is?(:always)
  249. return @always_force = true
  250. when is?(:quit)
  251. say "Aborting..."
  252. raise SystemExit
  253. when is?(:diff)
  254. show_diff(destination, yield) if block_given?
  255. say "Retrying..."
  256. when is?(:merge)
  257. if block_given? && !merge_tool.empty?
  258. merge(destination, yield)
  259. return nil
  260. end
  261. say "Please specify merge tool to `THOR_MERGE` env."
  262. else
  263. say file_collision_help
  264. end
  265. end
  266. end
  267. # This code was copied from Rake, available under MIT-LICENSE
  268. # Copyright (c) 2003, 2004 Jim Weirich
  269. 1 def terminal_width
  270. result = if ENV["THOR_COLUMNS"]
  271. ENV["THOR_COLUMNS"].to_i
  272. else
  273. unix? ? dynamic_width : DEFAULT_TERMINAL_WIDTH
  274. end
  275. result < 10 ? DEFAULT_TERMINAL_WIDTH : result
  276. rescue
  277. DEFAULT_TERMINAL_WIDTH
  278. end
  279. # Called if something goes wrong during the execution. This is used by Thor
  280. # internally and should not be used inside your scripts. If something went
  281. # wrong, you can always raise an exception. If you raise a Thor::Error, it
  282. # will be rescued and wrapped in the method below.
  283. #
  284. 1 def error(statement)
  285. stderr.puts statement
  286. end
  287. # Apply color to the given string with optional bold. Disabled in the
  288. # Thor::Shell::Basic class.
  289. #
  290. 1 def set_color(string, *) #:nodoc:
  291. string
  292. end
  293. 1 protected
  294. 1 def prepare_message(message, *color)
  295. spaces = " " * padding
  296. spaces + set_color(message.to_s, *color)
  297. end
  298. 1 def can_display_colors?
  299. false
  300. end
  301. 1 def lookup_color(color)
  302. return color unless color.is_a?(Symbol)
  303. self.class.const_get(color.to_s.upcase)
  304. end
  305. 1 def stdout
  306. $stdout
  307. end
  308. 1 def stderr
  309. $stderr
  310. end
  311. 1 def is?(value) #:nodoc:
  312. value = value.to_s
  313. if value.size == 1
  314. /\A#{value}\z/i
  315. else
  316. /\A(#{value}|#{value[0, 1]})\z/i
  317. end
  318. end
  319. 1 def file_collision_help #:nodoc:
  320. <<-HELP
  321. Y - yes, overwrite
  322. n - no, do not overwrite
  323. a - all, overwrite this and all others
  324. q - quit, abort
  325. d - diff, show the differences between the old and the new
  326. h - help, show this help
  327. m - merge, run merge tool
  328. HELP
  329. end
  330. 1 def show_diff(destination, content) #:nodoc:
  331. diff_cmd = ENV["THOR_DIFF"] || ENV["RAILS_DIFF"] || "diff -u"
  332. require "tempfile"
  333. Tempfile.open(File.basename(destination), File.dirname(destination)) do |temp|
  334. temp.write content
  335. temp.rewind
  336. system %(#{diff_cmd} "#{destination}" "#{temp.path}")
  337. end
  338. end
  339. 1 def quiet? #:nodoc:
  340. mute? || (base && base.options[:quiet])
  341. end
  342. # Calculate the dynamic width of the terminal
  343. 1 def dynamic_width
  344. @dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput)
  345. end
  346. 1 def dynamic_width_stty
  347. `stty size 2>/dev/null`.split[1].to_i
  348. end
  349. 1 def dynamic_width_tput
  350. `tput cols 2>/dev/null`.to_i
  351. end
  352. 1 def unix?
  353. RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i
  354. end
  355. 1 def truncate(string, width)
  356. as_unicode do
  357. chars = string.chars.to_a
  358. if chars.length <= width
  359. chars.join
  360. else
  361. chars[0, width - 3].join + "..."
  362. end
  363. end
  364. end
  365. 1 if "".respond_to?(:encode)
  366. 1 def as_unicode
  367. yield
  368. end
  369. else
  370. def as_unicode
  371. old = $KCODE
  372. $KCODE = "U"
  373. yield
  374. ensure
  375. $KCODE = old
  376. end
  377. end
  378. 1 def ask_simply(statement, color, options)
  379. default = options[:default]
  380. message = [statement, ("(#{default})" if default), nil].uniq.join(" ")
  381. message = prepare_message(message, *color)
  382. result = Thor::LineEditor.readline(message, options)
  383. return unless result
  384. result = result.strip
  385. if default && result == ""
  386. default
  387. else
  388. result
  389. end
  390. end
  391. 1 def ask_filtered(statement, color, options)
  392. answer_set = options[:limited_to]
  393. correct_answer = nil
  394. until correct_answer
  395. answers = answer_set.join(", ")
  396. answer = ask_simply("#{statement} [#{answers}]", color, options)
  397. correct_answer = answer_set.include?(answer) ? answer : nil
  398. say("Your response must be one of: [#{answers}]. Please try again.") unless correct_answer
  399. end
  400. correct_answer
  401. end
  402. 1 def merge(destination, content) #:nodoc:
  403. require "tempfile"
  404. Tempfile.open([File.basename(destination), File.extname(destination)], File.dirname(destination)) do |temp|
  405. temp.write content
  406. temp.rewind
  407. system %(#{merge_tool} "#{temp.path}" "#{destination}")
  408. end
  409. end
  410. 1 def merge_tool #:nodoc:
  411. @merge_tool ||= ENV["THOR_MERGE"] || git_merge_tool
  412. end
  413. 1 def git_merge_tool #:nodoc:
  414. `git config merge.tool`.rstrip rescue ""
  415. end
  416. end
  417. end
  418. end

target/rubygems/gems/tzinfo-data-1.2019.2/lib/tzinfo/data/definitions/Etc/UTC.rb

100.0% lines covered

8 relevant lines. 8 lines covered and 0 lines missed.
    
  1. # encoding: UTF-8
  2. # This file contains data derived from the IANA Time Zone Database
  3. # (http://www.iana.org/time-zones).
  4. 1 module TZInfo
  5. 1 module Data
  6. 1 module Definitions
  7. 1 module Etc
  8. 1 module UTC
  9. 1 include TimezoneDefinition
  10. 1 timezone 'Etc/UTC' do |tz|
  11. 1 tz.offset :o0, 0, 0, :UTC
  12. end
  13. end
  14. end
  15. end
  16. end
  17. end